【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了。