iOS应用杀死后间隔长时间重启UserDefaults被清空如何排查
问题可能诱因
- 多线程并发读写
NSUserDefaults:NSUserDefaults本身不是线程安全的,多线程同时执行写入、读取操作时容易导致对应的plist存储文件结构损坏,系统识别到损坏的plist后会直接删除整个文件并重建空白文件,所有存储数据都会丢失。 - 第三方SDK恶意/误操作UserDefaults域:部分第三方SDK会直接调用
removePersistentDomainForName:或setPersistentDomain:forName:方法,批量删除甚至直接覆盖整个标准UserDefaults的存储域,导致所有自定义存储的键值对丢失。 - 数据保护级别设置不合理:如果应用开启了数据保护功能,且UserDefaults存储文件的保护级别设置为
NSFileProtectionComplete,当设备处于锁定状态时,UserDefaults文件会被加密无法读取,此时启动应用会读到nil值,若代码逻辑判断为空就走首次启动流程,可能进一步误覆盖原有数据。 - 写入操作未实际落地:虽然调用了
synchronize方法,但该方法从iOS 10开始已经被苹果标记为不推荐使用,且仅为同步建议,无法保证100%立即持久化,如果写入后应用立即被系统回收资源,可能出现写入未落地的情况。
UserDefaults自动清空的常见场景
- 存储plist文件损坏:多线程冲突、写入过程中应用崩溃、系统异常断电等情况都可能导致plist结构损坏,系统无法解析时会直接删除整个损坏的plist文件,重建空白存储文件。
- 系统存储空间极端不足:iOS在存储空间告急时,会优先清理应用的偏好设置、缓存等可重建类数据,且该操作不会提前通知应用。
- 应用被卸载重装:包括用户手动卸载后重装、系统开启「卸载未使用的App」功能后自动卸载应用再重装、更换签名/Bundle ID覆盖安装等场景,都会清空原有UserDefaults数据。
- 全局存储域被主动删除/覆盖:任何代码(包括自身业务代码、第三方SDK代码)调用
removePersistentDomainForName:传入标准UserDefaults的域名,或者传入空字典调用setPersistentDomain:forName:,都会直接清空所有UserDefaults存储内容。
排查方案
- 加符号断点定位删除操作:给
-[NSUserDefaults removeObjectForKey:]、-[NSUserDefaults removePersistentDomainForName:]、-[NSUserDefaults setPersistentDomain:forName:]这三个方法添加符号断点,触发时查看调用栈,可直接定位到是业务代码还是第三方SDK执行了清空/覆盖操作。 - 查看系统日志确认损坏场景:用Mac自带的「控制台」App连接测试设备,过滤应用Bundle ID和
NSUserDefaults、plist、corrupt等关键词,查看是否有plist损坏、重置UserDefaults的相关日志,即可确认是否为文件损坏导致的清空。 - 验证数据保护场景:将应用退后台后锁屏静置1小时以上,不解锁设备直接点击应用启动,查看是否出现数据丢失的情况,可验证是否为数据保护级别过高导致的读取失败。
- 拦截所有UserDefaults操作:通过Runtime hook拦截所有对
NSUserDefaults的写入、删除操作,打印每次操作的key、值以及调用栈,可追溯到所有异常操作的来源。 - 检查线程安全逻辑:梳理所有UserDefaults的读写操作,确认是否存在后台线程并发读写的情况,建议将所有UserDefaults操作统一收敛到主线程执行,避免多线程冲突。
内容的提问来源于stack exchange,提问作者Sahithi
相关产品推荐
相关产品推荐

