Recently, I ran into some problems with data dependencies and garbage collection in my bespoke model classes. Several classes in my model code register as observers of other model classes using either KVO or via
Although I’m using garbage collection, I was trying to be a good boy by implementing a dispose method on some of these model classes so that they could de-register as observers when they were no longer an active participant of the model.
The problem with this approach is that various Cocoa classes, such as
NSUndoManager, don’t easily offer me the opportunity to invoke my
dispose method (say for example when the redo queue is cleared by registering a new invocation). There are a few different solutions to the problem. In my case, registering the “activation” and “deactivation” of my model classes (i.e. the adding and removing of themselves as observers) with the
NSUndoManager is the one I opted for.
In the course of implementing this code, I played around with using
NSObject's finalize method as a way to de-register my observers, rather than inducing the undo manager to invoke my own
dispose method. Generally speaking, I try to stay away from
finalize methods in garbage collected languages, and Objective-C is no exception. Apple cautions against the madness of using
finalize methods for performing any serious work.
All that said, it got me thinking about where exactly Apple expects objects to de-register their KVO and
NSNotificationCenter observers in the general case. The problem with remove observers in
finalize methods are several:
- You are forced to introduce a
finalizemethod where you otherwise wouldn’t have to. Not good for performance and can open up a Pandora’s box of non-determinism.
- The finalizer’s of objects collected in the same cycle are not guaranteed to run in any particular order. So de-registering object A as an observer of object B in object A’s
finalizemethod might happen after object B is already collected (i.e. when it’s invalid), leading to “Cannot remove an observer … for the key path … from … because it is not registered as an observer.”
- The de-registration of observers happens at a non-deterministic time (i.e. whenever the garbage collector gets around to running).
As far as items #1 and #2 are concerned, there is a better way, which is (wait for it…) to do nothing.
From the docs on
“As a convenience, when built with garbage collection, you do not need to remove any garbage collected observer as the system will do it implicitly.”
And as of the November seed of 10.6, this is also now true of KVO. The seed note “Automatic Removal of Finalized Key-Value Observers When Running Garbage-Collected (New since November seed)” describes the following:
“In Mac OS 10.6, explicit removal of observers when they’re finalized is now never necessary. KVO automatically removes observers as they’re collected. Actually, in Mac OS 10.6 all invocations of -[NSObject(NSKeyValueObserverRegistration) removeObserver:forKeyPath:] and -[NSArray(NSKeyValueObserverRegistration) removeObserver:fromObjectsAtIndexes:forKeyPath:] do virtually nothing when either the receiver or the observer is being finalized (so it’s not very bad for performance to leave them there in applications that still have to run on Mac OS 10.5).”
So at least in Snow Leopard after the November seed, you can just let it ride and the system will take care of cleaning up your KVO and
As for item #3, well it’s either playing games with regularly invoking
NSGarbageCollector's collectExhastively or implementing a dispose pattern.