Mobile Development 16 min read

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.

NetEase Media Technology Team
NetEase Media Technology Team
NetEase Media Technology Team
Mastering iOS KVO: Deep Dive into Implementation, Risks, and KVOController

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

iOSRuntimeObjective‑CObserverKVOKVOController
NetEase Media Technology Team
Written by

NetEase Media Technology Team

NetEase Media Technology Team

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.