Video icon 64
Learning to code? Skill up faster with our practical video courses. Start your free trial today.
Advertisement

Key-Value Observing with Facebook's KVOController

by
Student iconAre you a student? Get a yearly Tuts+ subscription for $45 →

Introduction

If you've ever worked with KVO (Key-Value Observing) in Cocoa, chances are that you've run into various kinds of issues. The API isn't great and forgetting to remove an observer may result in memory leaks or—even worse—crashes. Facebook's KVOController library aims to solve this problem.

What Is Key-Value Observing?

If you're new to key-value observing or KVO, I recommend that you first read Apple's developer guide on the topic or Mattt Thompson's article on NSHipster. To quote Apple's guide on KVO, "Key-value observing provides a mechanism that allows objects to be notified of changes to specific properties of other objects." Mattt defines KVO as follows, "Key-Value Observing allows for ad-hoc, evented introspection between specific object instances by listening for changes on a particular key path." The keywords are evented and key path.

Before we discuss the KVOController library, I'd like to take a moment to explore the KVO API.

Adding an Observer

I won't cover KVO in great detail in this tutorial, but it's important that you understand the core concept of KVO. The idea is simple. An object can listen to changes to specific properties of another object. The observing object is added by the target object as an observer for a specific key path.

Let's illustrate this with an example. If objectB wishes to be notified when the name property of objectA changes, then objectA needs to add objectB as an observer for the key path name. Thanks to Objective-C's verbosity, the code to accomplish this is pretty simple.

[objectA addObserver:objectB forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];

Responding to Changes

Whenever objectA's name property changes, observeValueForKeyPath:ofObject:change:context: is invoked. The first parameter is the key path that's being observed by objectB, the second parameter is the object of the key path, the third argument is a dictionary describing the changes, and the final argument is the context that was passed as the final argument of addObserver:forKeyPath:options:context:.

I hope you agree that this isn't very elegant. If you're making extensive use of KVO, the implementation of observeValueForKeyPath:ofObject:change:context: quickly becomes long and complex.

Removing an Observer

It's important to stop listening for changes when an object is no longer interested in receiving notifications for a specific key path. This is done by invoking removeObserver:forKeyPath: or removeObserver:forKeyPath:context:.

The issue that every developer runs into at some point is either forgetting to call removeObserver:forKeyPath: or calling removeObserver:forKeyPath: with a key path that isn't being observed by the observer. The reasons for this are manyfold and are the root of the problem many developers face when working with KVO.

If you forget to invoke removeObserver:forKeyPath:, you may end up with a memory leak. If you invoke removeObserver:forKeyPath: and the object isn't registered as an observer an exception is thrown. The cherry on the cake is that the NSKeyValueObserving protocol doesn't provide a way to check if an object is observing a particular key path.

KVOController to the Rescue

Luckily, the Cocoa team at Facebook was just as annoyed by the above issues as you are and they came up with a solution, the KVOController library. Instead of reinventing the wheel, the team at Facebook decided to build on top of KVO. Despite its shortcomings, KVO is robust, widely supported, and very useful.

The KVOController library adds a number of things to KVO:

  • thread-safety
  • painless removal of observers
  • support for blocks and custom actions

Requirements

Before we get started, it's important to stress that the KVOController library requires ARC and that the minimum deployment targets are iOS 6 for iOS and 10.7 for OS X.

Installation

I'm a big proponent of CocoaPods and I hope you are too. To add the KVOController library to a project using CocoaPods, add the pod to your project's Podfile and run pod update to install the library.

pod 'KVOController'

Alternatively, you can download the latest version of the library from GitHub and manually add the library by copying KVOController.h and KVOController.m to your project.

Examples

Initialization

The first thing you need to do is initialize an instance of the FBKVOController class. Take a look at the following code snippet in which I create a FBKVOController instance in a view controller's initWithNibName:bundle: method.

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    
    if (self) {
        _KVOController = [FBKVOController controllerWithObserver:self];
    }
    
    return self;
}

Note that I store a reference to the FBKVOController object in the view controller's _KVOController instance variable. A great feature of the KVOController library is that the observer is automatically removed the moment the FBKVOController object is deallocated. In other words, there's no need to remember to remove the observer as this is automatically done the moment the FBKVOController object is deallocated.

Adding an Observer

You have several options to start observing an object. You can take the traditional approach by invoking observe:keyPath:options:context:. The result is that observeValueForKeyPath:ofObject:change:context: is invoked whenever a change event takes place.

[_KVOController observe:person keyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];

However, the FBKVOController class also leverages blocks and custom actions as you can see in the following code snippets. I'm sure you agree that this makes working with KVO much more enjoyable.

[_KVOController observe:person keyPath:@"name" options:NSKeyValueObservingOptionNew block:^(id observer, id object, NSDictionary *change) {
    // Respond to Changes
}];
[_KVOController observe:person keyPath:@"name" options:NSKeyValueObservingOptionNew action:@selector(nameDidChange:)];

Removing an Observer

Even though the observer is automatically removed when the FBKVOController object is deallocated, it's often necessary to stop observing an object before the observer is deallocated. The KVOController library has a number of methods to accomplish this simple task.

To stop observing a specific key path of an object, invoke unobserve:keyPath: and pass the object and key path. You can also stop observing an object by invoking unobserve: and pass in the object you want to stop observing. To stop observing every object, you can send the FBKVOController object a message of unobserveAll.

No Exceptions

If you take a look at the implementation of the FBKVOController class, you'll notice that it keeps an internal map of the objects and key paths the observer is observing. The FBKVOController class is more forgiving than the Apple's implementation of KVO. If you tell the FBKVOController object to stop observing an object or key path that it isn't observing, no exception is thrown. That's how it should be.

Conclusion

Even though KVO isn't a difficult concept to grasp, making sure observers are properly removed and race conditions don't cause mayhem is the real challenge when working with KVO.

I encourage you to take a look at the KVOController library. However, I also advise you to get a good understanding of KVO before you use it in your projects so you know what this library is doing for you behind the scenes.

Advertisement