【iOS开发每日小笔记(四)】iOS 7中如何除去UIAlertView 规避delegate对象销毁后接收消息的crash

这篇文章是我的【iOS开发每日小笔记】系列中的一片,记录的是今天在开发工作中遇到的,可以用很短的文章或很小的demo演示解释出来的小心得小技巧。它们可能会给用户体验、代码效率得到一些提升,或是之前自己没有接触过的技术,很开心的学到了,放在这里得瑟一下。90%的作用是帮助自己回顾、记忆、复习。

 

测试组的同学想尽办法测我们的bug,尽心尽力。今天他们发现,在某些时候UIAlertView弹出后,不关闭UIAlertView直接点击home键退到iOS主界面,再次进入程序,点击刚刚的UIAlertView上的按钮,程序crash。根据我的经验,一看便知肯定是消息传给了被释放的对象,造成的crash

首先来解释一下背景,目前我所做的项目,是一个几十人同时在线的教育类项目,界面层次关系也比较冗杂。先且不谈设计是否合理,为了同步刷新状态等原因,我们在程序退出到后台时,会删除当前的若干界面,程序再次进入时,会从服务器获取当前数据和状态,再据此重新创建若干界面。而UIAlertView本身是不会因为你退出到后台后再进入而自动消失(dismiss)的。因此问题就来了,假如我的UIAlertView的回调对象(delegate)设置的是某个将要销毁的View,那么点击home键程序退到后台,这个View被销毁,delegate指向的内存区域就是被释放的区域,再进入程序,点击UIAlertView上的按钮,发送消息就必然crash。(可以参考我的demo:在这里https://github.com/pigpigdaddy/ClearAlertViewDemo

 

 1 #import "MySubView.h"
 2 
 3 @implementation MySubView
 4 
 5 - (id)initWithFrame:(CGRect)frame
 6 {
 7     self = [super initWithFrame:frame];
 8     if (self) {
 9         // Initialization code
10         
11         self.backgroundColor = [UIColor darkGrayColor];
12         
13         UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"testAlert" message:@"TEST" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
14         [alertView show];
15     }
16     return self;
17 }
18 
19 - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
20 {
21     
22 }
23 
24 @end

 

可以看到在我的自定义View中,创建了一个UIAlertView,他的代理指向了self。

而在AppDelegate中,我在每次退到后台时,将自定义View给删除了:

1 - (void)applicationWillResignActive:(UIApplication *)application
2 {
3     ViewController *rootViewController = (ViewController *)self.window.rootViewController;
4     [rootViewController removeSubView];
5 }

重新进来后,点击确定crash,这样就模拟了这个crash。

 

于是想到解决方法,1,程序退出到后台时设置delegate为nil,(请注意,OC中,向nil发送消息,是可以的。不会crash,也什么都不会发生);2,程序退到后台时,手动dismiss当前的UIAlertView。

尝试了第二条,因为第二条更合理,设为nil的确可以规避掉crash,但是你的点击只能将UIAlertView给dismiss掉,而点击选择的功能将失效。

我们知道iOS 7之前,可以通过UIApplication 的 windows获取到UIAlertView所在的window,然后消除UIAlertView(具体可看这篇文章:http://blog.csdn.net/u010889390/article/details/18499691 不过很抱歉此刻我手边没有iOS 6 的 SDK不能去再次验证,但是我的确在iOS5或iOS6这么做过)。但是iOS 7开始,你无法获取到这个windows了,因为UIAlertView展现方式做出了变化,简而言之就是你不能通过UIApplication拿到UIAlertView了。你唯一能做的就是在创建的地方保存一份UIAlertView实例的引用,然后在适合的地方再去做其他操作(如本例中的销毁View时,手动dismiss掉UIAlertView)。

回到程序中,将我们的自定义View的做这样的改变:

1 #import <UIKit/UIKit.h>
2 
3 @interface MySubView : UIView<UIAlertViewDelegate>
4 
5 @property (nonatomic, strong)UIAlertView *alertView;
6 
7 @end
 1 #import "MySubView.h"
 2 
 3 @implementation MySubView
 4 
 5 - (id)initWithFrame:(CGRect)frame
 6 {
 7     self = [super initWithFrame:frame];
 8     if (self) {
 9         // Initialization code
10         
11         self.backgroundColor = [UIColor darkGrayColor];
12         
13 //        UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"testAlert" message:@"TEST" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
14 //        [alertView show];
15         
16         self.alertView = [[UIAlertView alloc] initWithTitle:@"testAlert" message:@"TEST" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
17         [self.alertView show];
18     }
19     return self;
20 }
21 
22 - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
23 {
24     
25 }
26 
27 @end

然后重新定义ViewController中的removeSubView方法:

1 - (void)removeSubView
2 {
3     MySubView *view = (MySubView *)[self.view viewWithTag:1000];
4     if (view) {
5         [view.alertView dismissWithClickedButtonIndex:0 animated:YES];
6         [view removeFromSuperview];
7     }
8 }

运行后,退出到后台,再进入,此时的UIAlertView已经dismiss。不会再有用户因为点击了残留的UIAlertView而Crash了。

posted @ 2014-08-17 23:21  pigpigdaddy  阅读(1484)  评论(0编辑  收藏  举报