最近线上遇到一个归档[NSKeyedArchiver archiveRootObject: toFile:]
崩溃的问题, 修复历程颇为曲折, 直到最后也没找到具体的问题, 在此记录下具体的排查方向吧.
崩溃几率大概千万分之一级别, 崩溃堆栈如下:
1 |
|
崩溃代码的目的是想把一个字典存到磁盘上做缓存, 内容如下:
1 | { |
第一开始以为只是一个简单的野指针问题, item 在多线程环境下产生的野指针, 就简单做了一个深拷贝, 然后再从深拷贝之后的 item 转化成 json :{"property1" : "value1", "property2" : "value2", ... "property9" : "value9",}
, 用新的 json 插入到大字典中, 最后存储, 然后上线后, 崩溃数据一点都没有变.
此时意识到问题不简单, 又继续做了如下修改
- 检查了字典中的每一个对象, 确认涉及对象创建和释放的部分使用都是发生在一个串行队列中
[NSKeyedArchiver archiveRootObject: toFile:]
被标注为弃用, 换成新的[NSKeyedArchiver archivedDataWithRootObject:requiringSecureCoding:error:]
, 需要注意的是, 新老 api 的产物有小概率不兼容- 使用
@try @catch
包住出问题代码 - 发现存在多次连续保存的调用, 限制频次为 10 秒触发一次
- 只有数据改变时触发归档
- 使用
atomic
修饰涉及的属性 - 归档前, 打印字典
以上操作 只有第5/6两个修改大概减少了 70% 的野指针崩溃数量, 第 8 点可以确定传入的数据没问题, 但是仍然崩溃, 说明是 NSKeyedArchiver
内部存在问题.
后续跟进思路, 打算弃用NSKeyedArchiver
, 继续预研其他持久化工具
MMKV
: 微信团队基于mmap
内存映射的 key-value 组件, 但是也存在一些文件损坏的情况, 需要做好文件备份以及恢复Sqlite
: 存在数据库损坏, 且无法恢复YYDiskCache
: 在实践中发现用户主动杀死 app 时,YYDiskCache
监听退出通知, 然后释放缓存时候超时, 触发 MetricKit 上报问题, 并且它的磁盘缓存还是基于NSKeyedArchiver
以及Sqlite
实现的.