Tech Talk on KVO

April, 2022

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? āœ‰ļø nick @ this domain.

ā€” Nick Randall

Tagged with: