iOS Crash记事本
每每搞上架,总是被上架之后的崩溃搞,特此记录。
系统无关崩溃
- 私有API重名
- hexString
- 复现场景:打开App之后跳转到别的app,再回来容易复现
- 崩溃原因:NSData有私有扩展hexString,自己也对NSData进行了hexString扩展,实现还不一样,直接导致数据问题崩溃,这里比较恶心的是,因为 复现和加载扩展加载顺序 有关系,不是毕现的,最后通过一个用户的毕现路径解决了问题。
- 解决方案:更改函数名
- hexString
- 存储相关
- rocksdb未正常关闭
- 复现场景:用户压后台之后杀进程
- 崩溃原因:在系统willTerminate的时候将写数据操作dispatch到 低优先级队列,不卡住主线程,并且不及时close数据库,导致数据库正在进行写操作的时候进程结束,从而crash
- 其他信息:崩溃堆栈主线程有exit函数调用
- 解决方案:调整数据写的时机,并且及时close数据库
- rocksdb未正常关闭
- NSArray\NSDictionary插入空值
- 崩溃原因:他们就是不能插入空值,over
- 解决方案:提供安全的插入方法,插入的时候检查是否为空。
- watchdog超时闪退
- 复现场景:应用启动速度慢的时候容易复现,尤其是在iphone4/iphone4s这样的低端机上
- 崩溃原因:系统为了保证用户体验,给了一个watchdog等待的最长时间,如果超过这个时间无响应,就会直接watchdog超时退出
- 其他信息: exception code 0x8badf00d
- 解决方案:查看启动卡住主线程的原因,统统弄到非主线程去
- 多线程问题
- case: SPDYSession
- 复现场景:不好复现,多线程问题一般都不是毕现的
- 崩溃原因:客户端实现了单例,但其中一处调用没有走
sharedInstance
,而是直接调用的init函数,导致多线程共同访问一个变量,从而storeStrong的时候挂掉 - 其他信息
- 挂在某个变量storeStrong的时候
- 查看所有线程,总有相同调用函数的地方
- 解决方案:都调用
sharedInstance
- case: SPDYSession
特定系统崩溃
- nano_free
- 系统:10.0 ~ 10.1
- 复现场景:大量进出有内存泄露的页面,积累一定程序的内存泄露之后即可复现
- 崩溃原因:内存泄露触发了系统nano_free的bug,参见聊聊苹果的Bug - iOS 10 nano_free Crash
- 其他信息:总是挂在类似序列化,NSLog这样使用小内存的地方
- 解决方案:解决内存泄露
- nano_realloc
- 系统:10.2 ~
- 复现场景:大量进出有内存泄露的页面,积累一定程序的内存泄露之后即可复现
- 崩溃原因:内存泄露触发了系统nano_realloc的bug
- 解决方案:解决内存泄露
- xcode版本问题
- 崩溃原因:高系统安装低版本xcode打出来的包可能会导致各种诡异的问题
- 其他信息:一般跪在系统函数里面
- 解决方案:升级打安装包的xcode为最新版本
- UITableView
- 系统:7.0 ~ 8.0
- 复现场景:推送push,点击push创建了一个vc,但此时navigationController没有创建,无法持有vc,导致vc创建完就释放,该vc中还有tableview,导致tableview也被释放,但delegate和datasource没有置nil,导致野指针。
- 崩溃原因:在iOS7和iOS8,tableview的delegate并不是weak的,参见这里,默认是assign的,所以会野指针。
- 解决方案:dealloc中增加对delegate和datasource的置nil操作
无信息闪退
- 内存溢出
- 距传说,有些内存溢出是没有堆栈的
- 异常信号
- 复现场景:系统在网络异常或者其他场景发送SIGPIPE等信号,导致App退出,无堆栈,系统日志会有一行log
- 崩溃原因:iOS系统对SIGPIPE等的默认处理就是退出App,并且不记录任何信息,可以在MaxOS的terminal下
man signal
来查看所有的信号。 - 解决方案:在App启动,切前后台的时候调用ignoring_signals
- 8badf00d
这种闪退系统是有闪退日志的,但一般大家使用的crashreporter是补货不到这种闪退的,所以这里也暂且当成无信息闪退
- 复现场景:系统启动时间很长,在低端机容易复现
- 奔溃原因:iOS系统对系统启动时间做了限制,超过watchdog的限制,一般是20s的话,就是主动将app杀死,并抛出8badf00d错误
- 解决方案:减少App启动时候做的事情,尽量将非关键路劲做成lazy-load的模式
其他与崩溃紧密相关专题
- 内存泄露专题
- 异常:在OC中捕获异常要非常小心,因为ARC是在编译阶段帮忙插入内存管理的代码,如果异常直接走了catch分支,容易导致内存泄露。
- 动画:CAAnimation的Delegat是 强引用,一步小心就内存泄露了
- 循环引用
- block持有self,建议使用weakify
- a持有b,同时b也只有a,而且a、b都不是self,这种case比较不直观,要小心应对
class_copyPropertyList
/class_copyIvarList
等runtime函数调用:需要主动free,否则都是内存泄露
影响判断的可能case
- crash上报是否准确、及时
- 准确性
- 对比苹果的crash统计,看是否存在某种类型的crash没有上报
- 查看舆情用户的设置->隐私->诊断与用量->诊断与用量数据,看是否存在没有上报的crash日志
- 及时性
- crash最好在崩溃的时候马上上报,否则的话,不仅无法及时发现用户崩溃问题,而且对于patch的效果也不好评估,除非是微信、支付宝这样日活亿级别的app
- 统计的时候可能取得是上报的时间,不及时上报会影响统计
- 准确性
- crash统计是否正确
- crash统计需要统计crash发生时的信息,包括版本号、crash时间等等,或者至少也要保证这两个信息是准确的,不能统计上报时的信息
- crash分类是否准确,很多时候如果crash没有解成功,会导致分类错误
辅助crash判断的手段
客户端本地日志
这个非常重要,需要保证客户端本地日志的完整性、及时性和准确性,保证用户在异常退出的时候能及时记录下本地日志。
- 完整性: 好好写日志
- 及时性: 在以下时机flush日志.
- fishhook exit
- fishhook kill
- 压后台
- 即将退出
- 数据量超过一定条数
- 距离上次写超过一定时间
- 监听信号,在必要的时候flush,可以借助plcrashreporter
客户端埋点和服务端日志
将客户端埋点、服务端日志以及crash日志按时间顺序排列,方便查看问题,这不光对于查找crash原因有作用,再查找其他问题的时候也意义重大。
舆情用户
及时跟进舆情用户的反馈,询问用户crash发生的操作路径,发生现象以及频率,可以查看用户是否有崩溃的诊断日志。
灰度机制
iOS还是缺乏良好的灰度机制啊。
现有灰度机制:
- TestFlight,港真?竟然还要用户下个App
- 企业证书签名,如果有那么万来个忠实用户,也是非常可以的