[toc]
前言
字节团队最近分享的 iOS 稳定性问题治理:卡死崩溃监控原理及最佳实践 提到: NSUserDefaults
底层实现中存在直接或者间接的跨进程通信,在主线程同步调用容易发生卡死。
随之而来的问题就是:NSUserDefaults
还能用吗?
经过对底层分析后,笔者的研究结论是:可以在理解 NSUserDefaults
的特性后,再使用。
一、NSUserDefaults
是什么?
NSUserDefaults
是 iOS 开发者常用的持久化工具,通常用于存储少量的数据
示例:
|
|
二、NSUserDefaults
的特性是什么?
根据本文后续的测试,我们可以发现 NSUserDefaults
共计以下 3 个特性:
- 多线程安全
- 内存级别缓存
- 写操作会触发
xpc
通信
三、NSUserDefaults
是如何保证多线程安全的?
NSUserDefaults
内部在读写时,会通过锁 lock
保证读写安全
可以通过
b os_unfair_lock_lock
设置断点
四、NSUserDefaults
的性能怎么样?
虽然 NSUserDefaults
是磁盘持久化存储,但是因为缓存的存在,所以,不会频繁的进行磁盘 I/O
可以通过私有类
CFPrefsPlistSource
的实例获取所有缓存的内容
我们唯一需要考虑的因素避免数据过多
三、NSUserDefaults
触发 xpc
的场景是什么?
NSUserDefaults
与 如何监控 iOS 的启动耗时 提到的渲染过程类似,同样依赖 xpc 进行跨进程通信。
下面,我们通过添加合适的断点对相关流程进行简单的介绍
添加调试断点
|
|
准备测试代码
|
|
测试
通过运行测试代码,我们可以发现 +[NSUserDefaults(NSUserDefaults) standardUserDefaults] + 68
执行时,会创建名为 "com.apple.cfprefsd.daemon"
的 xpc_connection
随后,会通过 xpc_connection_send_message_with_reply_sync
发送一个信息
[defaults setObject:@"酷酷的哀殿1" forKey:@"key"];
执行时,同样会发送一个消息
经过测试,我们可以发现只有第一次初始化或者调用 set... forKey:
相关的方法时,才会触发多进程通信
所以,我们可以得到以下结论:
NSUserDefaults
写操作会触发 xpc
通信,读操作和 synchronize
不会触发;应该降低写操作
四、如何异步的持久化?
通过官方文档,我们可以发现 xpc
框架存在两个不会锁住当前的线程 API
所以,我们可以尝试通过以上两个 API 发送持久化信息
异步持久化 Demo
下面以笔者的 iOS 14.3
系统为例进行演示
|
|
通过控制台,我们可以发现通过 xpc
存储的数据 this is a test
可以通过 NSUserDefaults
读取出来
证明 xpc_connection_send_message_with_reply
可以成功将内容持久化
五、总结
本文通过分析 NSUserDefaults
的 3 个特性:1、多线程安全,2、内存级别缓存,3、写操作会触发 xpc
通信;可以得到以下结论:
只有在以下场景才适合选择 NSUserDefaults
作为数据持久化:
- 少量数据存储
- 偶尔修改
- 多线程安全