@property(weak) isn't KVO-compliant

ARC has handed us a wonderful new tool: weak variables. When the object pointed to by a weak variable is deallocated, the variable is set to nil.

Generally, we use this feature to avoid retain cycles, by declaring a @property as weak. Here's Apple's example of the syntax:

So far, so good. However there's a snake in the grass.

By declaring a @property as weak, it's now impossible for it to be KVO-compliant. Weak properties are implemented simply by marking the underlying ivar as weak. If it gets cleared out to nil, that is handled directly by the runtime, without any sort of notification. Callers of the property will correctly start to receive nil, but none of your code knows precisely when  it happened. Thus there's no good way for a KVO notification to be posted at the time.

Further disguising the problem, calling the setter method generally will post a KVO notification to any interested observers! The KVO system is not especially aware of weak variables/properties, and to its eyes you're observing what looks to be a perfectly normal setter method.

Update: Michael Jurewitz apparently considers this a bug (I wouldn't go quite that far myself), having filed rdar://problem/12219416

Update 2: As pointed out to me by Jamie Montgomerie, it seems Michael has a slightly different complaint to mine; he seems (it's hard to tell without the sample project) to be complaining that observing a property of a weakly held object should notify the observer when that object is deallocated. I don't see what notification could be delivered though, without introducing a new aspect to the changes dictionary that tells the observer "you need to stop now". Having the observation automatically and quietly torn down for you would be preferable in my book.

One of the golden rules of KVO is that you can only observe keys that are known to be KVO-compliant. This is especially true of the Cocoa frameworks, where Apple has only documented a handful of methods as being so. Anything outside of that, you're somewhat chancing your luck whether it's truly compliant.

With your own code, it's even easier to forget and start assuming all properties are KVO-compliant, as so much is compliant by default. Don't be drawn into that trap with weak properties, please!

What can we do about this?

In my view, KVO-compliance needs to move from something specified purely in documentation/comments, to being a proper keyword available to us for use in code. That would allow the compiler and static analyser to start protecting us better against mistakes like this. I've filed rdar://problem/12957246; maybe it'll see some traction one day.

In the shorter-term, it occurs me to that you could inject some debugging code to check for this particular mistake:

  1. Swizzle -[NSObject addObserver:…] so that it:
    1. Figure out the key being observed by breaking apart the key path
    2. Check if there's a @property known to the runtime corresponding to that key
    3. If the @property is declared as weak, complain (exception, abort, log, etc.)

Update: And Vladimir Grichina of Componentix has risen splendidly to the challenge, offering debugging code to do as I suggested. Thanks!

© Mike Abdullah 2007-2015