每每搞上架,总是被上架之后的崩溃搞,特此记录。

系统无关崩溃

  1. 私有API重名
    • hexString
      • 复现场景:打开App之后跳转到别的app,再回来容易复现
      • 崩溃原因:NSData有私有扩展hexString,自己也对NSData进行了hexString扩展,实现还不一样,直接导致数据问题崩溃,这里比较恶心的是,因为 复现和加载扩展加载顺序 有关系,不是毕现的,最后通过一个用户的毕现路径解决了问题。
      • 解决方案:更改函数名
  2. 存储相关
    • rocksdb未正常关闭
      • 复现场景:用户压后台之后杀进程
      • 崩溃原因:在系统willTerminate的时候将写数据操作dispatch到 低优先级队列,不卡住主线程,并且不及时close数据库,导致数据库正在进行写操作的时候进程结束,从而crash
      • 其他信息:崩溃堆栈主线程有exit函数调用
      • 解决方案:调整数据写的时机,并且及时close数据库
  3. NSArray\NSDictionary插入空值
    • 崩溃原因:他们就是不能插入空值,over
    • 解决方案:提供安全的插入方法,插入的时候检查是否为空。
  4. watchdog超时闪退
    • 复现场景:应用启动速度慢的时候容易复现,尤其是在iphone4/iphone4s这样的低端机上
    • 崩溃原因:系统为了保证用户体验,给了一个watchdog等待的最长时间,如果超过这个时间无响应,就会直接watchdog超时退出
    • 其他信息: exception code 0x8badf00d
    • 解决方案:查看启动卡住主线程的原因,统统弄到非主线程去
  5. 多线程问题
    • case: SPDYSession
      • 复现场景:不好复现,多线程问题一般都不是毕现的
      • 崩溃原因:客户端实现了单例,但其中一处调用没有走sharedInstance,而是直接调用的init函数,导致多线程共同访问一个变量,从而storeStrong的时候挂掉
      • 其他信息
        • 挂在某个变量storeStrong的时候
        • 查看所有线程,总有相同调用函数的地方
      • 解决方案:都调用sharedInstance

特定系统崩溃

  1. nano_free
    • 系统:10.0 ~ 10.1
    • 复现场景:大量进出有内存泄露的页面,积累一定程序的内存泄露之后即可复现
    • 崩溃原因:内存泄露触发了系统nano_free的bug,参见聊聊苹果的Bug - iOS 10 nano_free Crash
    • 其他信息:总是挂在类似序列化,NSLog这样使用小内存的地方
    • 解决方案:解决内存泄露
  2. nano_realloc
    • 系统:10.2 ~
    • 复现场景:大量进出有内存泄露的页面,积累一定程序的内存泄露之后即可复现
    • 崩溃原因:内存泄露触发了系统nano_realloc的bug
    • 解决方案:解决内存泄露
  3. xcode版本问题
    • 崩溃原因:高系统安装低版本xcode打出来的包可能会导致各种诡异的问题
    • 其他信息:一般跪在系统函数里面
    • 解决方案:升级打安装包的xcode为最新版本
  4. 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操作

无信息闪退

  1. 内存溢出
    • 距传说,有些内存溢出是没有堆栈的
  2. 异常信号
    • 复现场景:系统在网络异常或者其他场景发送SIGPIPE等信号,导致App退出,无堆栈,系统日志会有一行log
    • 崩溃原因:iOS系统对SIGPIPE等的默认处理就是退出App,并且不记录任何信息,可以在MaxOS的terminal下man signal来查看所有的信号。
    • 解决方案:在App启动,切前后台的时候调用ignoring_signals
  3. 8badf00d 这种闪退系统是有闪退日志的,但一般大家使用的crashreporter是补货不到这种闪退的,所以这里也暂且当成无信息闪退
    • 复现场景:系统启动时间很长,在低端机容易复现
    • 奔溃原因:iOS系统对系统启动时间做了限制,超过watchdog的限制,一般是20s的话,就是主动将app杀死,并抛出8badf00d错误
    • 解决方案:减少App启动时候做的事情,尽量将非关键路劲做成lazy-load的模式

其他与崩溃紧密相关专题

  1. 内存泄露专题
    • 异常:在OC中捕获异常要非常小心,因为ARC是在编译阶段帮忙插入内存管理的代码,如果异常直接走了catch分支,容易导致内存泄露。
    • 动画:CAAnimation的Delegat是 强引用,一步小心就内存泄露了
    • 循环引用
      • block持有self,建议使用weakify
      • a持有b,同时b也只有a,而且a、b都不是self,这种case比较不直观,要小心应对
    • class_copyPropertyList/class_copyIvarList等runtime函数调用:需要主动free,否则都是内存泄露

影响判断的可能case

  1. crash上报是否准确、及时
    • 准确性
      • 对比苹果的crash统计,看是否存在某种类型的crash没有上报
      • 查看舆情用户的设置->隐私->诊断与用量->诊断与用量数据,看是否存在没有上报的crash日志
    • 及时性
      • crash最好在崩溃的时候马上上报,否则的话,不仅无法及时发现用户崩溃问题,而且对于patch的效果也不好评估,除非是微信、支付宝这样日活亿级别的app
      • 统计的时候可能取得是上报的时间,不及时上报会影响统计
  2. crash统计是否正确
    • crash统计需要统计crash发生时的信息,包括版本号、crash时间等等,或者至少也要保证这两个信息是准确的,不能统计上报时的信息
    • crash分类是否准确,很多时候如果crash没有解成功,会导致分类错误

辅助crash判断的手段

客户端本地日志

这个非常重要,需要保证客户端本地日志的完整性、及时性和准确性,保证用户在异常退出的时候能及时记录下本地日志。

  • 完整性: 好好写日志
  • 及时性: 在以下时机flush日志.
    • fishhook exit
    • fishhook kill
    • 压后台
    • 即将退出
    • 数据量超过一定条数
    • 距离上次写超过一定时间
    • 监听信号,在必要的时候flush,可以借助plcrashreporter

客户端埋点和服务端日志

将客户端埋点、服务端日志以及crash日志按时间顺序排列,方便查看问题,这不光对于查找crash原因有作用,再查找其他问题的时候也意义重大。

舆情用户

及时跟进舆情用户的反馈,询问用户crash发生的操作路径,发生现象以及频率,可以查看用户是否有崩溃的诊断日志。

灰度机制

iOS还是缺乏良好的灰度机制啊。

现有灰度机制:

  1. TestFlight,港真?竟然还要用户下个App
  2. 企业证书签名,如果有那么万来个忠实用户,也是非常可以的

餐后甜点