将旧版KVO代码迁移至Swift 4的技术咨询
在Swift 4中修复KVO依赖与方法调用问题
首先,Swift 4对KVO的要求比旧版本严格很多——毕竟它依赖Objective-C Runtime,所以我们得先把基础条件捋顺:
1. 确保类与属性符合KVO要求
你的类必须继承自NSObject,并且要把参与KVO的属性(包括被依赖的state和受影响的目标属性)暴露给OC Runtime:
- 最简单的方式是给类加上
@objcMembers标记,这样所有属性和方法自动适配OC Runtime; - 或者单独给每个相关属性加
@objc dynamic(dynamic是关键,它强制Swift通过OC Runtime派发属性,这是KVO生效的前提)。
2. 正确声明属性依赖关系
针对你提到的“state变化影响其他属性”的需求,Swift 4里有两种规范的实现方式:
方式一:重写全局依赖方法
这种方式适合多个属性依赖同一个state的场景:
@objcMembers class YourClass: NSObject { // 被依赖的属性必须加dynamic dynamic var state: Int = 0 // 受影响的计算属性(如果是存储属性也一样) var isReady: Bool { return state == 2 } override class func keyPathsForValuesAffectingValue(forKey key: String) -> Set<String> { let baseKeyPaths = super.keyPathsForValuesAffectingValue(forKey: key) // 当查询isReady的依赖时,返回state的key path if key == "isReady" { // 用#keyPath语法避免字符串拼写错误 return baseKeyPaths.union([#keyPath(state)]) } return baseKeyPaths } }
方式二:针对单个属性的专用依赖方法
如果只有某个特定属性依赖state,用这种方式更简洁:
@objcMembers class YourClass: NSObject { dynamic var state: Int = 0 var isReady: Bool { return state == 2 } // 专门为isReady声明依赖,方法名必须是keyPathsForValuesAffecting+属性名(首字母大写) @objc class func keyPathsForValuesAffectingIsReady() -> Set<String> { return [ #keyPath(state) ] } }
3. 确保观察者方法能被调用
如果你的observeValue(forKeyPath:of:change:context:)方法没被触发,还要检查这几点:
- 注册观察者时,务必使用正确的key path(推荐用
#keyPath语法,比如#keyPath(YourClass.isReady)); - 观察者类也要继承自
NSObject,并且observeValue方法不能被标记为private; - 记得在观察者销毁前移除观察者(比如在
deinit方法里调用removeObserver(_:forKeyPath:))。
举个完整的观察者实现例子:
class ObserverClass: NSObject { var observedObject: YourClass! init(observed: YourClass) { self.observedObject = observed super.init() // 注册观察者,指定要观察的key path和需要的选项 observedObject.addObserver(self, forKeyPath: #keyPath(YourClass.isReady), options: [.initial, .new], context: nil) } override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { guard keyPath == #keyPath(YourClass.isReady), let change = change else { super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) return } // 处理属性变化 let newValue = change[.newKey] as? Bool print("isReady updated to: \(newValue ?? false)") } deinit { // 移除观察者,避免野指针 observedObject.removeObserver(self, forKeyPath: #keyPath(YourClass.isReady)) } }
关于你提到的Open Radar Bug
如果以上步骤都做了还是有问题,可能是Swift 4早期版本的KVO边缘场景Bug——比如针对final类、或者某些特殊计算属性的问题。这种情况下,可以尝试:
- 确保你的类不是
final(KVO需要Runtime生成子类来拦截属性访问); - 把受影响的计算属性也标记为
@objc dynamic(虽然计算属性本身不需要dynamic,但某些Bug场景下加了能解决问题); - 更新到较新的Swift 4.x版本(苹果后续修复了不少KVO相关的Bug)。
内容的提问来源于stack exchange,提问作者Perlguy
相关产品推荐
相关产品推荐

