Tech Talk on KVO

I recently came across the slides for a talk that I presented in 2017 on the subject of Key-Value Observing. While it may not be as relevant today, I still thought it was quite a good presentation (if I may say so myself 😁) and wanted to share it here.

I’ll add my comments below each section in a block quote like so.


A Totally Impartial Look At Key-Value Observing

KVO sucks

More Specifically

KVO Is A Bad Idea, Implemented In A Terrible Way

 

Ok maybe a little harsh but it never hurts to get people’s attention at the start of a talk 🙂

While I am aware that KVO underpinned some important features like Cocoa Bindings and may work fine for small teams using it in a very disciplined way, when used in a large app with many developers contributing, it’s a nightmare.

In my opinion KVO is a rare miss for Apple’s APIs and should probably never have been ported to iOS.


What’s So Bad About It?

It’s Super Fragile

  • Accidentally forgot to remove an observation before the observed object was deallocated? CRASH
  • Removed an observation before adding it? CRASH
  • Removed an observation twice? CRASH
  • Deallocated the observer before removing the observation? CRASH

It Has No Protection

  • Can I find out who is observing an object? NOPE
  • Can I find out which objects I am observing? NOPE
  • Can I at least just remove all my observers? HAHA, NOPE
  • Can I determine if a property even supports KVO? NAH
  • Can I easily break my superclass? YEP
  • Can I break behaviour of my subclasses? SURE!

It’s Painful To Use

  • Can’t keep the observer creation code and the observation handler together
  • Can’t find out which object changed along a keypath
  • Can’t observe weak pointers being nilled
  • Doesn’t support fancy modern features like blocks, queues, weak references or even custom selectors
  • To use it safely, needs lots of boilerplate: context objects, calls to super, keypath checks, flags to track observation

I’ve experienced all of these crashes more than once over the years. Since the code to register, observe and unregister can be spread all over a big view controller, it’s extremely easy to break by accident.

The API for KVO has remained essentially unimproved since the very first iOS SDK. Which is odd considering that even the confusingly-similar NSNotificationCenter API got some minor updates in iOS 4 with support for these “fancy modern features” like blocks and queues 😄


KVO Is Not A Great Idea Anyway

  • Introduces invisible dependencies
  • Almost impossible to find out which parts of the code might be observing your object
  • Hard to notice if you break KVO compliance
  • Relies on objects that you don’t control the lifetime of
  • Keypath observing breaks loose coupling

The notion that KVO encourages bad software design is nothing new, but that hasn’t stopped people from attempting to fix its shortcomings for a long time.


OK I Get The Picture

So What Do I Use Then?

Literally Anything Else

  • Notifications
  • Callback blocks
  • Target / action
  • Delegates
  • Swift KVO 😒

I don’t really consider KVO in Swift to be much of an improvement conceptually, but at least the API uses strong key paths, completion blocks and unregistration on deallocation.


But I Really Want To Use KVO

If You Must Use KVO, Use This

self.observer =
    [self addObserverForKeyPath:SELFKEY(prop.subprop)
                        options:KVOSendInitialValue
                          block:^(id newVal, id oldVal) {
                              NSLog(@"Now %@", newVal);
                          }];

or:

self.observer =
    [self addObserverForKeyPath:SELFKEY(prop.subprop)
                        options:KVOSendEqualValues
                       selector:@selector(valueChanged:)];

Now we get to the point of the talk, it wasn’t just a rant 😆

I not only wanted to discourage usage of KVO, but provide a safer alternative to the existing uses of it in our codebase.


NSObject+KVObserver

Why It Sucks Less

  • Robust, safe, and simple to use
  • Observation is tied to lifetime of returned token
    • Automatically removed when the token is nilled
    • Usually you don’t have to remove observers at all
  • Enforces good KVO practices
    • Can only observe properties of objects you own
    • All observation handlers are called on the main thread

While my KVO helper worked quite well (improving even on Mike Ash’s effort), I won’t be going through the source code for it as usual. It didn’t really use any interesting features of the ObjC runtime, and honestly it takes some pretty gnarly code to make KVO play nice, which I’m not keen to show off.


The presentation ended with a screenshot of a KVO crash in Fabric, the dreaded “cannot remove an observer for a key path because it is not registered as an observer” exception.

70,000 crashes in a week. 😞

Any comments or questions about this post? Ping me on Twitter 💬

Nick Randall — April, 2022

Tagged with: