UserDefaults键路径Publisher仅触发首次值,SwiftUI接收却正常?
UserDefaults扩展属性订阅异常问题
问题场景
给UserDefaults扩展Objective-C兼容的动态属性:
@objc dynamic var someProp: String { string(forKey: "someProp") }
用常规方式订阅该属性的发布者,仅能获取初始值,后续更新无法触发回调:
private let sub = UserDefaults.standard.publisher(for: \.someProp).sink { print($0) }
但在SwiftUI中通过.onReceive观察同一个发布者,却能正常接收所有后续更新:
.onReceive(pub) { value in print("received", value) }
最小复现示例
public extension UserDefaults { @objc dynamic var value1: Int { integer(forKey: "string1") } } struct ContentView: View { @StateObject var vm = ViewModel() private let pub = UserDefaults.standard.publisher(for: \.value1) var body: some View { Button("Add") { var value = UserDefaults.standard.value(forKey: "value1") as? Int ?? 0 value += 1 debugPrint("SET", value) UserDefaults.standard.set(value, forKey: "value1") } .onReceive(pub) { value in debugPrint("UI", value) } } class ViewModel: ObservableObject { private let sub = UserDefaults.standard.publisher(for: \.value1).sink { debugPrint("SUB", $0) } } }
原因分析
KVO触发条件不满足:
你扩展的属性只有getter方法,没有对应的setter。当调用UserDefaults.set(_:forKey:)时,是直接操作UserDefaults的底层存储,并没有触发扩展属性的setter。而publisher(for:)底层依赖KVO机制,KVO仅在属性的setter被调用时才会发送通知,所以常规订阅拿不到后续更新。SwiftUI的特殊处理:
SwiftUI视图在生命周期内会自动刷新,.onReceive会在每次视图刷新时重新建立订阅,因此能获取到最新值;而ViewModel中的订阅是一次性创建的,初始订阅后没有收到KVO通知,就不会再触发回调。
解决办法
给扩展的属性添加完整的getter和setter,让值的修改通过属性的setter完成,以此触发KVO:
public extension UserDefaults { @objc dynamic var value1: Int { get { integer(forKey: "value1") } set { set(newValue, forKey: "value1") } } }
之后修改值时直接操作该属性:
UserDefaults.standard.value1 += 1
这样无论是ViewModel中的sink订阅,还是SwiftUI的.onReceive,都能正常接收所有更新通知。
另外注意:原复现代码中属性getter使用了错误的Key("string1"),需改为与属性名一致的"value1",避免读取数据错误。
内容的提问来源于stack exchange,提问作者Martin
相关产品推荐
相关产品推荐

