You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

将旧版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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 10:50:55