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
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-similarNSNotificationCenter
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