I'd like to monitor property value change of a managed object in Core Data for data integrity purpose (for example, if a specific property changes, another object in a relationship should be changed accordingly). I'm thinking to implement it using KVO and let the managed object observe its own property change. Below is my code.
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!")
}
}
}
(Note: the observation
variable is a transient attribute of the managed object to keep a strong reference to the NSKeyValueObservation
object.)
My question is what's the appropriate place to remove observers? Or is it necessary at all? I did try the above code and it worked fine. But I'm not sure if it just happened to work and may have potential issue when code becomes complex.
I have figured out the following after some research:
1) App should remove KVO observers before a NSObject is deallocated. For example, the doc says:
An object that calls this method must also eventually call either the removeObserver(:forKeyPath:) or removeObserver(:forKeyPath:context:) method to unregister the observer when participating in KVO.
Note: the doc is about addObserver()
, not the API I'm using, but I think the idea is same.
2) The new KVO API in Swift removes the observer automatically when NSKeyValueObservation
object is deinitialized (i.e., when it's out of scope or not referenced by others).
So I thought I should do this:
extension Book {
deinit {
observation = nil
}
}
But unfortunately that is impossible because XCode gave this error:
Deinitializers may only be declared within a class
So I wonder what's the usual way to do it? Could it be that, since the the observer and observee are same object, the underlying KVO framework code notices that observer object is also being deallocated, so it's OK without removing the observer explicitly?
Any suggestion will be much appreciated.
Update 1:
@pbasdf suggests using willTurnIntoFault
. I google about it and find few discussions which indicate people using it this way. My main confusion: is fault a required a state before a NSManagemObject
is deallocated? I don't find Apple doc says so (the doc doesn't have a state transition diagram).
EDIT: According to this post, when a NSManagedObjectContext is tore down, it turns all objects registered with it back into faults. If so, fault does always occur before deallocation.
BTW, while reading Apple doc, I also find this:
When Core Data turns an object into a fault, key-value observing (KVO) change notifications are sent to the object’s properties. If you are observing properties of an object that is turned into a fault and the fault is subsequently realized, you receive change notifications for properties whose values have not in fact changed.
That leads me to think using KVO with NSManagemObject
is perhaps not a ideal approach because it is interfered by fault.
Update 2:
Since the observer and observee is the same object in my case, there is a much simpler approach than using KVO. That's defining a wrapper of property setter method and implementing all the logic in the wrapper. I'll use this approach.
That said, I still think my question is valid in general case where observer and observee are different objects.
Update 3:
@matt pointed out that it's OK to not remove KVO observers since iOS11. See another discussion here and official doc here.
Relaxed Key-Value Observing Unregistration Requirements
Prior to 10.13, KVO would throw an exception if any observers were still registered after an autonotifying object's -dealloc finished running. Additionally, if all observers were removed, but some were removed from another thread during dealloc, the exception would incorrectly still be thrown. This requirement has been relaxed in 10.13, subject to two conditions:
- The object must be using KVO autonotifying, rather than manually calling -will and -didChangeValueForKey: (i.e. it should not return NO from +automaticallyNotifiesObserversForKey:)
- The object must not override the (private) accessors for internal KVO state
If all of these are true, any remaining observers after -dealloc returns will be cleaned up by KVO; this is also somewhat more efficient than repeatedly calling -removeObserver methods.