Skip to content

Latest commit

 

History

History
59 lines (36 loc) · 5.04 KB

中文介绍.md

File metadata and controls

59 lines (36 loc) · 5.04 KB


前一段时间看了网易大白健康管理系统的文章,感觉很不错,文中对iOS app常见的几种crash进行了总结和分析,并提出了修复方案。平时开发过程中,笔者也略有积累,对一些潜在的crash也有自己的处理方案。利用业余时间开发完成一个简单轻易的开源库,专门处理几种常见的crash,虽还不完善,秉承回馈开源社区,为大家做下分享。

项目命名为Dr.Light,灵感来自于去年看的漫威电影《惊奇博士》。目前修复了以下几种情况:

UI 非主线程刷新

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

KVO 非对称添加删除

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

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

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

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

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错误信息。
解决方案:hookUINavigationControllerpushViewController:animated:方法,设置一个时间间隔,记录每次push的时间,如果距离上次push时间间隔较短,则禁止push操作。

unrecognized selector

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

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

2017-10更新 最近发现一个严重问题,工程引入库后,会再使用textfield等键盘弹起时候直接崩溃。因此在 methodSignatureForSelector内做了一个简单拦截,如果是和keyboard相关的事件不处理。

安装

CocoaPods

pod 'DrLight'

手动

把需要的头文件和实现文件拖拽到自己的工程下

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

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

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