Tech Talk on KVO

Sat, 2 Apr 2022 12:34:00 +0200

A tech talk from the archives, extolling the virtues of Key-Value Observing.

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

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