Swift Combine:计算型变量仅在扩展中触发Publisher?原因何在?
首先需要明确:你的UserDefaultsManager应该继承自UserDefaults(而非NSObject,否则代码无法编译,因为NSObject没有float(forKey:)这类方法)。问题的核心在于UserDefaults的动态方法解析机制与子类属性的KVO兼容性:
原因分析
1. 主类体中的@objc计算属性无法被监听的原因
UserDefaults是特殊的NSObject子类,它内部通过Objective-C动态方法解析,将属性访问直接映射到自身存储的键值对。当你在UserDefaults子类的主类体中定义@objc计算属性时,UserDefaults的初始化逻辑会优先把这些属性与对应的存储键绑定,导致KVO系统无法将该属性识别为独立的可观察对象——而Combine的publisher(for:)完全依赖KVO机制,自然接收不到变化通知。
2. 扩展中的@objc计算属性能正常工作的原因
Swift扩展是在类的主定义之后添加的,UserDefaults的动态方法解析不会处理扩展中新增的属性。此时,这个@objc计算属性会被Objective-C运行时正确注册为子类的独立属性,KVO系统能够正常识别并监听其变化。当你通过UserDefaultsManager.shared.musicVolume = newValue设置值时,扩展中的setter被调用,虽然内部还是调用UserDefaults的set(_:forKey:),但KVO会将这个setter调用视为属性的变化,从而触发Combine Publisher的事件。
补充验证
如果在主类体中给计算属性添加dynamic关键字,也能让Combine Publisher正常工作——dynamic强制属性使用动态派发,绕过了UserDefaults的静态方法解析拦截:
class UserDefaultsManager : UserDefaults { @objc dynamic var musicVolume: Float { get { float(forKey: "music_volume") } set { set(newValue, forKey: "music_volume") } } }
本质原理
UserDefaults的内部机制会拦截主类体中定义的@objc属性,将其映射为键值存储的访问器,而非作为子类的独立属性。扩展中的属性不受这个拦截影响,因此能被KVO正常识别,进而被Combine的Publisher监听。
内容的提问来源于stack exchange,提问作者gbread

