防止crash的一些方法

  1. UI 非主线程刷新
    如果UI的刷新没有在主线程发生,导致的后果包括可能会有crash、动画失效、界面无反应或者异常等。
    解决方案:利用runtime,hook了view的setNeedsLayout、setNeedsDisplay、setNeedsDisplayInRect:、setNeedsUpdateConstraints四个方法,判断当前是否是主队列,如果不是主队列,则跳转到dispatch_get_main_queue执行。

2.KVO 非对称添加删除
我个人在响应式编程中偏爱使用KVO,它带来的益处不言自明。如果因为疏忽导致add observer/remove obsever不成对出现,可能会导致crash。
解决方案:hook了NSObject的addObserver:forKeyPath:options:context:、removeObserver:forKeyPath:。为被观察对象增加一个map属性,存储所有的observer和keypath的对应关系。每次添加或者删除观察者的时候,都会先去检查observer的keypath是否已存在或者消失,在执行相当逻辑。避免重复添加或者删除。

下面展示添加观察者的简单流程图,删除类似。


防止crash的一些方法_第1张图片
Paste_Image.png

注意:使用前需要设置被观察者的kvoSafteyToggle为true,原因是因为许多系统操作会执行kvo操作,没必要为系统kvo增加保护,浪费内存。 facebook的开源库kvocontroller可以解决此问题,还能保证线程安全,相比其本项目更轻,代码量更少,有兴趣的可以看下。

完善:被观察者销毁的时候,是否还有残留观察者,这个项目没有加。如果做的话方案是hookdealloc加判断。

3.navigationcontroller的连续push
先说一个常见的例子,比如点击某个按钮push到一个新的界面,如果连续快速点击,界面就会发生连续的push,这种情景下不一定会发生crash。我见过网上有一种解决方案,也是利用运行时hook了button的sendaction事件,增加时间戳或者计数器,来阻止短时间点击事件的连续调用。个人觉得这个方案虽然能解决问题,但只是浮于表面,甚至可以说是本末倒置。button并没有做错什么却要“背锅”,应该让真正的”肇事者“navigation去处理。

button action ----->  push viewcontroller

crash的情景,在viewcontroller的viewDidLoad执行pushViewController方法,由于此时界面布局尚未完成,会发生nested push崩溃,用umeng或者bugly会收集到cannot addsubView:self错误信息。
解决方案:hookUINavigationController的pushViewController:animated:方法,设置一个时间间隔,记录每次push的时间,如果距离上次push时间间隔较短,则禁止push操作。

4.unrecognized selector
未知选择器,具体原因和系统的消息转发机制有关,这里不在专门讲述。
解决方案:hook NSObject的methodSignatureForSelector:和forwardInvocation:。创建一个单例对象,在forwardInvocation:中将消息转交该单例处理。利用runtime为单例中增加一个方法,增加一个目标selector的新指向IMP(具体实现为空),这样单例就能像安全地全盘接收无效selector。

开始的时候打算把消息的拦截处理放在forwardingTargetForSelector中,毕竟这里提供转发对象就可以了,而且这个函数的调用时机比上面两个都早,理论上越早拦截无效操作越好。不过开发过程中,我发现许多系统级函数例如getServerAnswerForQuestion:reply:居然也会出现在forwardingTargetForSelector里,这让我有些意外。无奈之下,只能将拦截操作下沉。

总结
还有几种错误,比如数组越界,空指针插入dictionary。这种情景跟业务牵扯较大,即使加了处理,但后续界面展现也可能造成困扰,建议开发者在开发阶段就增加判断。

NSString *str  = [@"Tom Hanks" substringFromIndex:10];
_label.text = str;

目前功能还待继续完善,欢迎拍砖。日后会增加swift版并上传到pod。用心写一篇文章也是比较费心费神,如果觉得本文有帮助,欢迎点赞。

你可能感兴趣的:(防止crash的一些方法)