If you’re sure you need an observer, Cocoa lets you choose between Notification Center and KVO. Which should you use? That’s easy. Notification Center. Avoid KVO. Thanks, and see you next month!
Learning Cocoa, a lot of great engineers told me, “don’t use KVO,” and left it at that. Maybe they didn’t want to get into a lecture about architecture, and for years, implementation issues were enough dealbreakers:
- The single method signature is error-prone
- The context parameter is error-prone
- String typing is error-prone
- Performance is unpredictable
But even with a full rewrite, KVO’s underlying pattern makes things are harder to maintain and debug. Ad-hoc property-observation is just a bad idea.
Object.observe proposal, which its authors withdrew:
Complex apps ended up “creating tens of thousands of O.o calls,” he told InfoQ. “This was tricky to debug, and had strange performance characteristics. On Chrome, setup time was long but runtime was fast. On polyfilled browsers, the opposite was true.”
KVO sounded like a great idea, so why the dead end?
Hidden Side Effects
Consider this innocuous code:
You wouldn’t know there are hidden side effects from updating that avatar property. Maybe it updates a tableview cell, and maybe that incurs an expensive draw operation. In KVO heavy apps, you spend an inordinate ammount of time wondering, “What is doing this?”
Notifications explicitly yield control. While we don’t know the consequences, if we’re curious, we can search for instances of that notification. That little hint is the difference between ‘annoying to follow’ and ‘spooky action from a distance.’2
The Wire Tap
Matt Drance said, “The observer pattern typically involves a defined contract. KVO’s worst smell for me is that it’s more of a wiretap.”
KVO makes it very easy to poke holes in encapsulation. We see this all the time with sneaky iOS developers observing views in the iOS keyboard. You shouldn’t dig through Apple’s private view hierarchies, and you certainly shouldn’t rely on their internal behavior.
Regardless of whether the object belongs to Apple or yourself, KVO fools you into thinking you have an API when you don’t. In some future version, code churn changes internal behavior, and the assumptions you built your hacky API on fly out the window.
KVO reminds me of Rails’ delight with Monkey Patching, Ruby’s version of swizzling. “We don’t need formal APIs,” proclaimed the 10x developers, “we can build our plugins on top of monkey patches!” Then Rails would release an update, and plugins relying on internal behavior broke.
Say you’re writing an email client, and there’s a
MailSender class outside your control:
You’re writing a mail-throttler. You measure how long it takes to send an email, and if things are moving fast, enqueue more email. You do it by measuring the time between
handleNextMessage, and when
isIdle becomes true.
“A perfect situation for KVO,” you think. Unfortunately, your throttler can’t observe
isIdle directly, because it isn’t key-value compliant.
“Wait, we can observe the
outbound property? Close enough!”
It works fine, until the owner of
Now when our observer is called,
isIdle is incorrect— the message has been popped off the queue, but it’s still in-flight. Your throttler thinks messages are sent instantaneously. Bugs ensue.
Next, the author of
MailSender notices the momentary inconsistency, but only when multithreading. How did do most people fix threading? Slap a lock on it!
Now your observer deadlocks. If you can find a way around that, it gets better.
Why did the author make the class threadsafe? Because they’re moving the operation off the main thread! Since KVO notifications fire on the thread that calls the setter, suddenly you’re touching UI from the background.
The real solution is to make
isIdle KVO compliant, and document it as such.
The problem is that people don’t write safe, reentrant code by default. Compare the complexity of the final, KVO compliant version with the simplicity of the first version.
In contrast, it’s usually safe to fire a notification at the end of method, after state settles:
When we define
MailSenderDidUpdateNotification, we make a contract about its behavior. If it changes, we’re on the hook. We can document, test, and deprecate it as need be.
If the object you’re observing does not document itself as KVO compatible, don’t assume it is. Unfortunately, that brings us to KVO’s worst flaw.
The Accidental Programming Interface
UIKit’s classes do not support KVO. According to the documentation (emphasis mine):
Note: Although the classes of the UIKit framework generally do not support KVO, you can still implement it in the custom objects of your application, including custom views.
Or from Apple Staff on the developer forums:
In general UIKit objects do not support KVO. Unless a class is documented as supporting KVO, you should assume it does not (even if it appears to work – there are likely edge cases that you haven’t noticed).
I bet most iOS developers don’t know that. Some do, but think it’s “safe enough.” Meanwhile, if a poor UIKit engineer makes a change in this unsupported behavior, you’d see a mob outside Apple HQ with torches and pitchforks.
KVO’s biggest mistake was baking support directly into
NSObject. Most of the time it just works. But if nobody is actually checking, compatibility is an accident.
Swift uses a better default. You have to mark properties with
dynamic for the machinery to work, making KVO opt-in. Maybe it would be clearer if they were marked
observable, but given dynamic dispatch is mostly used for KVO and swizzling, when you mark it
dynamic you have a good idea of what you’re getting into.
Sometimes, you have no choice but to use KVO, like if you’re using
NSProgress. Proceed with caution.
If you’re designing an API yourself, and you’re sure you should use the observer pattern, use notification center.
Thanks to everyone who replied to my tweet asking for input.