如何让UserDefaults发布器在多线程环境下稳定工作?
问题描述
我正在开发一个系统,需要在UserDefaults值发生变化时同步至另一个系统。尝试用UserDefaults.standard.publisher监听特定值的变化,但最初连更新都收不到。之后在多线程环境下测试UserDefaults的线程安全性,编写了示例代码却无法解释运行行为。想请教:如何让发布器始终能接收到更新值,同时保证线程安全?
示例代码
import Combine import Foundation let key = "value" private extension UserDefaults { @objc dynamic var value: Int { get { integer(forKey: key) } set { set(newValue, forKey: key) } } } print("Starting") let dispatchGroup = DispatchGroup() dispatchGroup.enter() dispatchGroup.enter() DispatchQueue.global().async { UserDefaults.standard.value = 0 let publisher = UserDefaults.standard.publisher(for: \.value, options: .new) let cancellable = publisher.sink { value in print("Received: \(value)") } UserDefaults.standard.value = 1 UserDefaults.standard.setValue(2, forKey: key) DispatchQueue.global().async { UserDefaults.standard.setValue(3, forKey: key) dispatchGroup.leave() } dispatchGroup.leave() } dispatchGroup.wait() print("Finished")
输出
Starting Received: 1 Received: 1 Received: 2 Finished
问题分析与解决方案
核心问题拆解
1. 订阅过早被销毁
代码中cancellable是闭包内的局部变量,当外层异步闭包执行完毕后,该变量会被系统释放,订阅随之取消。这就是最后设置的值3无法被接收的原因。
2. 重复接收值的原因
你混合使用了自定义动态属性value和原生setValue(_:forKey:)方法修改数据:
- 通过
value = 1赋值时,会触发KVO通知,发布器收到1 - 调用
setValue(2, forKey:)时,绕过了自定义属性的setter,导致KVO通知逻辑出现异常,额外收到一次旧值1,之后才触发新值2的通知
解决方案
1. 保留订阅生命周期
将订阅对象存储在全局或类级别的容器中,避免被过早释放:
// 全局或类属性,用于保存订阅 var cancellables = Set<AnyCancellable>()
订阅时通过store(in:)方法保存:
publisher .sink { value in print("Received: \(value)") } .store(in: &cancellables)
2. 统一数据修改方式
避免混合使用自定义属性和原生方法,统一通过扩展的动态属性修改UserDefaults值,保证KVO通知的一致性:
// 所有修改都用这个属性 UserDefaults.standard.value = 3
3. 保证线程安全
苹果官方明确UserDefaults本身是线程安全的,但并发读写可能导致更新顺序混乱。可以用串行队列封装所有UserDefaults操作,严格保证执行顺序:
private let userDefaultsQueue = DispatchQueue(label: "com.yourapp.userdefaults") // 线程安全的写操作 func setValueSafely(_ value: Int) { userDefaultsQueue.sync { UserDefaults.standard.value = value } } // 线程安全的读操作 func getValueSafely() -> Int { return userDefaultsQueue.sync { UserDefaults.standard.value } }
修改后的完整代码
import Combine import Foundation let key = "value" var cancellables = Set<AnyCancellable>() private extension UserDefaults { @objc dynamic var value: Int { get { integer(forKey: key) } set { set(newValue, forKey: key) } } } private let userDefaultsQueue = DispatchQueue(label: "com.example.userdefaults") func setValueSafely(_ value: Int) { userDefaultsQueue.sync { UserDefaults.standard.value = value } } print("Starting") let dispatchGroup = DispatchGroup() dispatchGroup.enter() dispatchGroup.enter() DispatchQueue.global().async { setValueSafely(0) let publisher = UserDefaults.standard.publisher(for: \.value, options: .new) publisher .sink { value in print("Received: \(value)") } .store(in: &cancellables) setValueSafely(1) setValueSafely(2) DispatchQueue.global().async { setValueSafely(3) dispatchGroup.leave() } dispatchGroup.leave() } dispatchGroup.wait() print("Finished")
修改后的输出
Starting Received: 1 Received: 2 Received: 3 Finished
内容的提问来源于stack exchange,提问作者timfraedrich
相关产品推荐
相关产品推荐

