Mastering iOS KVO: Deep Dive into Implementation, Risks, and KVOController
This article explains how iOS Key-Value Observing works under the hood, enumerates common pitfalls across iOS versions, and shows how the open‑source KVOController library mitigates those issues while providing safer, block‑based observation APIs.
KVO Overview
Key‑Value Observing (KVO) lets an object watch changes to a property of another object. A typical usage pattern involves three steps: adding an observer, implementing observeValueForKeyPath:ofObject:change:context:, and removing the observer when it is no longer needed.
How KVO Is Implemented
When an object is observed, the Objective‑C runtime creates a hidden subclass of the observed class (e.g., NSKVONotifying_Stock) and swaps the object's isa pointer to this subclass. The subclass overrides the property's setter (e.g., setPrice:) and several NSObject methods ( class, superclass, etc.) to inject change notifications.
- (void)setPrice:(int)price {
[self willChangeValueForKey:@"price"];
// call original setter implementation
(*imp)(self, _cmd, price);
[self didChangeValueForKey:@"price"];
}The overridden class method masks the isa‑swizzling so that [object class] still returns the original class name.
KVO Pitfalls
Missing observation callback method – crashes on iOS 10 and later.
Removing observers more times than added – crashes on iOS 10 and later.
Observer not removed before the observed object is deallocated – crashes on iOS 10 and earlier, safe on later versions.
Thread‑safety issues can produce wild‑pointer crashes when dealloc and change notifications race.
KVOController Introduction
KVOController (also known as FBKVO) is an open‑source wrapper that provides a safer, block‑based API on top of native KVO. Its main benefits are:
Readable callbacks via blocks, delegates, or the original method.
Automatic removal of observers, eliminating manual removeObserver: crashes.
Thread‑safe handling that avoids observer resurrection problems.
KVOController Implementation Details
When a controller observes stock, it creates an FBKVOInfo object containing the key path, options, and callback block. This info is passed to a singleton _FBKVOSharedController, which registers the observation with the runtime.
- (void)observeValueForKeyPath:(nullable NSString *)keyPath
ofObject:(nullable id)object
change:(nullable NSDictionary<NSString *, id> *)change
context:(nullable void *)context {
if (info->_block) {
info->_block(observer, object, change);
} else if (info->_action) {
[observer performSelector:info->_action withObject:change withObject:object];
} else {
[observer observeValueForKeyPath:keyPath ofObject:object change:change context:info->_context];
}
}Removal is guarded: the controller checks whether a key‑path was previously added before calling removeObserver:, preventing over‑removal crashes. Deallocation of the controller automatically removes all registered observations.
- (void)dealloc {
_FBKVOSharedController *shared = [_FBKVOSharedController sharedController];
for (id object in _objectInfosMap) {
NSSet *infos = [_objectInfosMap objectForKey:object];
[shared unobserve:object infos:infos];
}
}How KVOController Solves Native KVO Issues
Blocks prevent missing callback crashes.
Guarded add/remove logic avoids over‑removal crashes.
Automatic removal in dealloc eliminates the “observer not removed before deallocation” problem.
The singleton shared controller never deallocates, removing the multithreaded race where an observer becomes a dangling pointer.
Correct Usage Guidelines
Choose between KVOController (strong reference to the observed object) and KVOControllerNonRetaining (weak reference) based on object lifetimes:
If the observer’s lifetime is longer than the observed object, use KVOControllerNonRetaining and manually remove observations when appropriate.
If you want automatic removal and the observed object can safely outlive the observer, use KVOController.
Typical scenarios:
Observer and observed objects have matching lifetimes – KVOController works without manual removal.
Observed object outlives the observer – prefer KVOControllerNonRetaining and call unobserveAll in the observer’s dealloc.
Conclusion
Understanding the runtime mechanics of KVO and the trade‑offs of KVOController helps developers avoid common crashes and choose the right observation strategy for their iOS projects.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
