Garbage Collection and KVO and NSNotificationCenter, Oh My!

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 NSNotificationCenter.

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:

  1. You are forced to introduce a finalize method where you otherwise wouldn’t have to. Not good for performance and can open up a Pandora’s box of non-determinism.
  2. 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 finalize method 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.”
  3. 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 NSNotificationCenter's addObserver:

“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 NSNotificationCenter registrations.

As for item #3, well it’s either playing games with regularly invoking NSGarbageCollector's collectExhastively or implementing a dispose pattern.

Published in: on October 23, 2009 at 8:56 am  Comments (1)  
Tags: , , , ,

Faulting Fun

Managed memory is awesome. Generally speaking, I would rather gouge my eyes out than go back to manually allocating memory.

It’s not that I can’t do it. I’ve been programming for long enough that I started out manually managing memory in the progression from 6502 assembly to C to C++, etc. True, the past few years I’ve been much farther away from embedded system code than I used to be in the days of the Super Nintendo, Atari Jaguar, PlayStation, et al. It’s just that I’d rather spend my time expressing “big ideas” (cue eye rolling now) than tweaking out string memory management on my sub three pound laptop with 4GB of memory.

I’m not sure I would be able to handle Cocoa and Objective-C programming without automatic memory management, but one recent friction point in my love affair with the Cocoa APIs is the dawning realization that there are a ton of subtle API architecture and implementation choices that were made before the move to Objective-C 2.0 and managed memory.

I’m not talking about fundamental incompatibilities between Cocoa API architecture and managed memory. I’m just talking about stumble inducing speed bumps.

A recent example I’ve tripped over is in combining Core Data and KVO. I am using both raw KVO and data binding heavily to glue together Core Data with the view model and view of the application I’m working on. In one instance, when a Core Data document (well, really an associated NSManagedObjectContext) is loaded, I execute a fetch request to obtain a set of peer “root” NSManagedObjects. I then register to observe some relationships and attributes on the resultant NSManagedObjects via KVO and then continue on with the rest of the application.

Later during the program, when new “root” NSManagedObjects of this particular entity type are created, I do the same thing (i.e. register to observe some relationships and attributes using KVO).

When attributes on these recently added “root” objects are changed, the user interface reflects the changes as expected, however when attributes on loaded “root” objects are changed, the user interface does not reflect the changes. WTF right?

After a curse filled hour or two (and copious amounts of caffine for focus) the problem turned out to be with my original fetch request and register using KVO. When I performed the fetch, I was not keeping a reference to the NSManagedObjects I obtained. Eventually after a garbage collection cycle or two, those initial NSManagedObjects were collected, and the underlying Core Data entity instances were turned back into fault (i.e. are dumped from memory). A very cool feature of Core Data, but very not ideal in this case.

When I later executed a fetch request to obtain one of these initially loaded “root” NSManagedObjects, Core Data created new NSManagedObject instances for me, bringing the data they represent back into memory from the persistent store. At this point, they are NOT the same NSManagedObject instances that I registered to observe with KVO (those instances are gone) because KVO did not keep strong references to the original objects, and neither did I.

Bottom line, while I am conceptually changing values on the same Core Data entity instances that I initially registered to watch with KVO, they are not actually the same NSManagedObject instances (thanks to garbage collection and the efficiency / coolness of Core Data dumping data from memory that’s not currently needed). Hence the lack of change in the user interface. The observed objects didn’t change, they were garbage collected!

In this case, it’s an easy fix; I just retain strong references to my “root” NSManagedObjects. I don’t really want these objects coming in and out of memory anyway.

I suspect had garbage collection been in heavy use by the designers and creators of KVO and Core Data when they were conceived and implemented, there might have been some subtle changes to their approach. Or maybe at the very least to the documentation.

I’m not really complaining; both of these systems are damn cool and when I’m not learning how to avoid tripping over my own feet, they are accelerating the crap out of my development.

Here’s looking forward to the days of less tripping per stride!

Published in: on May 19, 2009 at 6:19 pm  Comments (4)  
Tags: , , , ,
Follow

Get every new post delivered to your Inbox.