Core Data中托管对象KVO观察者的移除时机与必要性探讨
先看你给出的监听Book对象date属性变化的代码:
extension Book { override public func awakeFromInsert() { super.awakeFromInsert() observation = self.observe(\.date, options: [.old, .new]) { book, dateChange in print("date is changed!") } } override public func awakeFromFetch() { super.awakeFromFetch() observation = self.observe(\.date, options: [.old, .new]) { book, dateChange in print("date is changed!") } } }
(注:observation是托管对象的临时属性,用于强引用NSKeyValueObservation对象)
核心问题:观察者的移除时机与必要性
Swift新KVO的自动清理特性
你说的没错,Swift的新KVO API(返回NSKeyValueObservation的observe(_:options:changeHandler:)),当这个NSKeyValueObservation对象被销毁时,会自动移除观察者。这和旧版addObserver/removeObserver的逻辑不同,理论上不需要手动调用移除方法——前提是你能正确管理observation的生命周期。Core Data托管对象的特殊限制
你在扩展里加deinit失败是正常的,Swift不允许在类扩展中声明析构函数。针对NSManagedObject子类,业界常见的处理方式是利用willTurnIntoFault()方法:在对象被Core Data转为fault状态时,把observation设为nil。- 通常情况下,
NSManagedObject被释放前确实会进入fault状态——比如上下文销毁、对象被移出上下文,或者内存紧张时都会触发。但这里有个坑:如果对象从fault状态再次被实例化(比如访问它的属性),你需要重新创建观察者(好在你已经在awakeFromFetch里做了这件事)。 - 另外苹果文档也提到,Core Data把对象转为fault时会发送KVO变更通知,这可能导致你收到“假的”属性变更回调,这也是KVO在Core Data场景下不够理想的原因之一。
- 通常情况下,
iOS 11+的兜底机制
正如@matt提到的,在iOS 11和macOS 10.13及以后,系统对KVO的容错性做了提升:只要你的对象使用KVO自动通知(没有重写+automaticallyNotifiesObserversForKey:返回NO),也没有覆盖内部KVO状态的私有访问器,那么即使对象dealloc后还有观察者注册,KVO框架会自动清理这些观察者,不会再抛出异常。这意味着如果你的部署目标在这个版本之上,即使没手动处理observation的销毁,也不会崩溃,但从代码严谨性来说,还是建议尽量管理好observation的生命周期。
更适合你的替代方案
因为你的场景是观察者和被观察者是同一个对象,完全没必要用KVO,直接自定义属性的setter包装方法更简单可靠:
extension Book { var wrappedDate: Date { get { date } set { willChangeValue(forKey: #keyPath(date)) date = newValue didChangeValue(forKey: #keyPath(date)) // 在这里执行你的关联对象变更逻辑 print("date is changed!") } } }
如果是Xcode生成的Core Data属性,也可以直接在类本身(而非扩展)里重写属性的setter,在setter中加入你的业务逻辑,完全避开KVO的各种坑。
观察者与被观察者不同的通用场景
如果是不同对象之间的KVO监听,常规做法是:
- 把
NSKeyValueObservation作为观察者对象的属性持有(比如在ViewModel里持有对Model对象的观察),当观察者对象被销毁时,observation也会被销毁,自动移除监听。 - 如果是Core Data场景下跨对象监听,比如监听另一个托管对象的属性变化,同样可以把
observation持有在观察者对象中,在观察者的deinit里置为nil(如果是普通类的话),或者在willTurnIntoFault里处理(如果也是托管对象的话)。
内容的提问来源于stack exchange,提问作者rayx

