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

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

这篇文章是我的【iOS开发每日小笔记】系列中的一片,记录的是今天在开发工作中遇到的,可以用很短的文章或很小的demo演示解释出来的小心得小技巧。该分类的文章,内容涉及的知识点可能是很简单的、或是用很短代码片段就能实现的,但在我看来它们可能会给用户体验、代码效率得到一些提升,或是之前自己没有接触过的技术,很开心的学到了,放在这里得瑟一下。其实,90%的作用是帮助自己回顾、记忆、复习。如果看官觉得太easy,太碎片,则可以有两个选择:1,移步【iOS探究】分类,对那里的文章进行斧正;2,在本文的评论里狠狠吐槽,再关掉页面!感谢!

 

测试组的同学想尽办法测我们的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 code10         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)buttonIndex20 {21     22 }23 24 @end

 

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

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

1 - (void)applicationWillResignActive:(UIApplication *)application2 {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 code10         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)buttonIndex23 {24     25 }26 27 @end

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

1 - (void)removeSubView2 {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了。