Skip to content
KVO with a block instead of a callback
Objective-C Ruby
Find file
Pull request Compare This branch is 10 commits behind master.
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.



A category on NSObject that allows you observe a keypath passing in a block to execute when the keypath changes instead of using a callback method.

This works on iOS and OS X.

Why? Blocks are funky. They let you define an action at the same time as you are setting up a observation. In most cases this leads to clearer code than using a callback.


  • ARC. This is written to be dropped into a ARC project, there are no macros to gracefully handle using it in MRC or GC apps
  • Since it needs ARC, it needs OS X 10.7 and iOS 4.


Quite simple, there are only three methods provided by the category


This is the main method for registering for an observer. It takes a keyPath and options as is usual for registering KVO observations, but additionally it takes an NSOperationQueue and a block. The block is run on the queue when a change is obseverved to the key path. If the queue is nil, then the block is run on the calling thread.

The block has no return value and is passed in the change dictionary for the observation. It is typedefed as:

typedef void (^jcsObservationBlock)(NSDictionary *change);

When registering for observation, an opaque object reference is returned, which is used to unregister for the observation.


// Assume you have an iVar: id _observer to hold the opaque reference
NSOperationQueue *queue = [NSOperationQueue new];
_observer =
[self jcsAddObserverForKeyPath:@"self.stringProperty" options:NSKeyValueObservingOptionNew queue:queue block:^(NSDictionary *change) {
    NSLog(@"New string value %@", [change objectForKey:NSKeyValueChangeNewKey]);


This is a convenience method that runs the provided block on the calling queue. The options are NSKeyValueObservingOptionNew so that the change dictionary has a key of NSKeyValueChangeNewKey.

When registering for this observation, an opaque object reference in returned, which is used to unregister for the observation.


// Assume you have an iVar: id _observer to hold the opaque reference
_observer =
[self jcsAddObserverForKeyPath:@"self.stringProperty" withBlock:^(NSDictionary *change) {
    NSLog(@"New string value %@", [change objectForKey:NSKeyValueChangeNewKey]);


This is used to remove the observation object using the opaque reference returned when registering. At the very least this should be done in the dealloc method. It is safe to attempt to unregister an observer that has already been unregistered.


// Assuming an iVar: id _observer
- (void)dealloc {
    [self jcsRemoveObserver:_observer];


The development branch (the branch structure is described below) contains an Xcode Workspace with two example projects that show simple observations for blocking and non-blocking observers.

Branch structure for submodules

There are two branches to this repository, master and development, these make it easier to use the same repository for developing as well as for sharing the code as a Git submodule.

The master branch

The master branch just contains the class category and this README file. It is the one to use if you want to add it as a submodule to your project. This should be treated as a readonly branch. do not perform any development on this branch.

if you are using this as a submodule, keep up to date with the latest changes by

The development branch

The development branch contains the category files as well as Xcode projects for development and demonstration. This is the branch where development should be performed, the changes push back to the master branch cleanly through the magic of careful merging.

To add the development branch rather than master, simply use the -b flag when cloning, as shown below.

git submodule add -b development

There are Unit Tests for the category in each of the OS X and iOS projects, but these are shared between the two projects.


Sometimes, there may be artefacts left over when switching from master to development. These are files that are ignored by Git and are easily cleaned up by running

git clean -dxf

License (MIT)

Standard MIT licence.

I don't need attribution, it's only a small bit of code. Be inspired to write your own block based replacements.


Based on:

I make no apologies for taking the bits I like from them.

Something went wrong with that request. Please try again.