A Delegate-like Observer Mechanism in Objective-C using Forward Invocation

August 14th, 2011 · 10 Comments · Objective-C

The observer pattern is useful whenever you want a dynamic list of objects (observers) be notified when an event occurs in an observed object (also called the subject).

Cocoa provides several built-in ways to make use of this pattern. If you are interested in a profound comparison, Matt Gallagher has a great summary of the standard methods for observation and notification in Cocoa.

In this post I am going to contribute an implementation of the observer pattern that resembles the way delegates are implemented in Cocoa. In contrast to Cocoa built-in mechanisms (like NSNotificationCenter) the presented approach enables you to use arbitrary selectors in notification messages (just as is the case with delegates). What is more you won’t have to cope with the details how notification messages are dispatched by the broadcaster.

Note: for what it’s worth, I will call the broadcaster “observable” and the listener “observer” throughout this post. This is in line with the classes I’m providing in my sample code.

Basic Idea

We would like to be able to notify multiple observers about a certain event, giving them a chance to act as appropriate. Just like with delegates, this should be possible using arbitrary selectors.

Let’s take a look at a standard delegation example in Objective-C:

- (void)performTask
{
    // Check whether delegate implements -willPerformTask
    if ([delegate respondsToSelector: @selector(willPerformTask)]) {
        // Notify delegate that we're about to perform the task
        [delegate willPerformTask];
    }
}

The example above, of course, is limited to one delegate receiving notification messages from the delegator. Now, what we want in certain situations is a collection of observers (multiple “delegates”). All observers should receive the notification message.

It would be neat if the code in your observable object would look something like this:

- (void)performTask
{
     // Notify observers that we're about to perform the task
     [self.observers willPerformTask];
}

The idea here is that observers acts as a proxy broadcasting the willPerformTask message to all observer objects implementing the message’s selector.

This is exactly what the Observers class that I provide in the Observer sample code will do for you.

A Closer Look

Let’s take a closer look at what we’re talking about here.

The Observer sample provides two classes responsible for implementing observer registration and message broadcasting:

  • Observable: Abstract base class giving subclasses the ability to easily adopt the role of “generic” broadcasters. Observable maintains an Observers object and allows other objects to add and remove observers.
  • Observers: This class implements the actual broadcasting mechanism for Objective-C messages to arbitrary observer objects (must be Objective-C objects.) The basic idea here is that all messages sent to Observers are automatically broadcasted to all observers responding to a given selector (implementing/listing to a given notification message.)

An Example

For what follows, let’s assume the following example: we’d like to implement a simple contact manager that is capable of managing a collection of contact names. The contact manager provides two methods, -addContactWithName: and -removeContactWithName:. What is more, our contact manager should be observable, i.e. an arbitrary number of observers should be notified whenever a contact is being added or removed.

Implementing an Observable Contact Manager

Our contact manager is defined in the ContactManager class which inherits from Observable so as to provide for observation capabilities (see ContactManager.h in the sample code):

#import "Observable.h"
@interface ContactManager : Observable {
    NSMutableArray *contactNames;
}
- (void)addContactWithName:(NSString *)name;
- (void)removeContactWithName:(NSString *)name;
@end

By inheriting from Observable, ContactManager is equipped with an Observers object which is capable of maintaining a collection of observers and broadcasting messages to them.

As a consequence, within ContactManager‘s implementation of -addContactWithName: we can do the following (see ContactManager.m in the sample code):

- (void)addContactWithName:(NSString *)name
{
    [contactNames addObject:[name copy]];
    [self.observers contactManager:self
             didAddContactWithName:[name copy]];
}

-removeContactWithName: is implemented likewise:

- (void)removeContactWithName:(NSString *)name
{
    [contactNames removeObjectIdenticalTo:name];
    [self.observers contactManager:self
         willRemoveContactWithName:[name copy]];
}

Note: Observable provides two methods to add and remove observers:

  • -(void)addObserver:(id)observer; and
  • -(void)removeObserver:(id)observer;.

Since ContactManager is a Observable subclass, we can later wire observers to the contact manager by using these methods.

Implementing a Contact Manager Observer

The observer may be defined as a simple NSObject subclass providing implementations for the notification selectors it is interested in. Consider the following example (see ContactObserver.h and ContactObserver.m in the sample code):

@interface ContactObserver : NSObject
@end

@implementation ContactObserver
- (void)contactManager:(ContactManager *)manager
 didAddContactWithName:(NSString *)name
{
    NSLog(@"Observer got notification: contact manager added contact with name %@", name);
}

- (void)contactManager:(ContactManager*)manager
willRemoveContactWithName:(NSString *)name
{
    NSLog(@"Observer got notification: contact manager is about to remove contact with name %@", name);
}
@end

Wiring Observers with Observables

Finally, what we have to do to kick things off is wire the observer with the observable. In our example this looks as follows (see main.m in the sample code):

ContactManager *manager = [[ContactManager alloc] init];
ContactObserver *observer = [[ContactObserver alloc] init];
[manager addObserver:observer];
// [...]

Providing a Syntactically Correct Implementation with Protocols

While the code presented above will work, it is likely to cause compiler warnings due to the fact that observers are of type id. The compiler cannot know for sure that observers are going to respond to messages like contactManager:didAddContactWithName:, because id does not implement these methods.

The best solution here is to provide a protocol for each type of observer. This is not strictly necessary, but it tidies up your code and supports a good understanding of how things are structured.

It’s a good idea to add the protocol to the Observable’s header and name it accordingly.

In ContactManager.h, we add:

@protocol ContactManagerObserver
@optional
- (void)contactManager:(ContactManager *)manager
 didAddContactWithName:(NSString *)name;
- (void)contactManager:(ContactManager *)manager
willRemoveContactWithName:(NSString *)name;
@end

Consequently, the observer class should implement the newly defined protocol.

In ContactObserver.h we then have:

#import "ContactManager.h"
@interface ContactObserver : NSObject <ContactManagerObserver>
- (void)contactManager:(ContactManager *)manager
 didAddContactWithName:(NSString *)name;
- (void)contactManager:(ContactManager *)manager
willRemoveContactWithName:(NSString *)name;
@end

Finally, we need to change how ContactManager sends notification messages to observers.

In ContactManager.m, consider the “add contact” example employing the protocol as follows:

- (void)addContactWithName:(NSString *)name
{
    [contactNames addObject:[name copy]];
    [(id<ContactManagerObserver>)self.observers contactManager:self
                                         didAddContactWithName:[name copy]];
}

Viola, by telling the compiler that observers conform to the ContactManagerObserver protocol, there’s nothing to complain about anymore.

Characteristics of the Presented Observer Mechanism

There are some key characteristics of what you can do with observers when implementing them in the way discussed here:

  • Arbitrary method signatures: you may use selectors with arbitrary signatures for notification messages.
  • No obligation to handle all observer messages: it is not necessary to implement all methods for handling messages sent by the observable object. That is, in our example an observer may implement -contactManager:didAddContactWithName: only, omitting -contactManager:didRemoveContactWithName:.
  • Order of notification message dispatch undefined: you should keep in mind that the order in which notifications are sent to observers is not defined with this approach. As a consequence, you should avoid making assumptions on what other observers do in response to an observable notification.
  • Weak references to observers: the Observers class holds weak references to observer objects to avoid retain cycles. Consequently, you must take care of the observer vs. observable lifecycle on your own.

Limitations

There are some limitations with the presented approach:

  • Observer method implementations cannot return values: due to the nature of the broadcaster mechanism employed here, observer methods must exhibit a void return type.
  • Sample code is not optimized for performance: you should not use this approach for performance critical operations. The Observers class implements broadcasting in a fairly naive way, asking all registered observer objects whether they respond to the given selector. For large numbers of observer, this may represent a major performance hit. You should consider using another method in such cases.

Behind the Scenes: How Does Message Forwarding Work?

There is a plethora of documentation on Objective-C message forwarding, and I am not going to re-iterate on that here. However, to give you a basic understanding of what the code in the Observers class does, I provide a brief explanation here. And also there’s one little thing to add that has not yet been discussed profoundly on the web yet—swallowing selectors with an unknown method signature—but more on that later.

Message Forwarding and How To Swallow Messages With Unknown Signature

At the heart of the Observers message forwarding mechanism, there are two methods: methodSignatureForSelector: and forwardInvocation:.

These methods are called by the Objective-C runtime whenever a selector is sent to an object which is not implemented inside the compiled code of the receiver’s class.

Essentially, the runtime first calls methodSignatureForSelector:. If it gets back a valid method signature, it continues with calling forwardInvocation:. This gives custom code a chance to implement message forwarding for messages not implemented directly in the receiver’s class.

Be wary, that the runtime will cause an exception (unrecognized selector sent to instance) when methodSignatureForSelector: returns nil.

Let’s have a look at Observer implementation of methodSignatureForSelector: first:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
    if(!signature) {
        for(id observer in observers) {
            if([observer respondsToSelector:aSelector])
                return [observer methodSignatureForSelector:aSelector];
        }
        signature = [NSMethodSignature signatureWithObjCTypes: "@^v^c"];
    }
    return signature;
}

This method is called by the Objective-C runtime to request a method signature for a given selector. Observers implements this method so as to ask each registered observer whether it is capable of responding to the given selector. If so, the method signature is requested from the first observer exhibiting that capability.

If no observer capable of responding is found, we have to quietly bailout—without causing an exception.

This is exactly what the following line does:

signature = [NSMethodSignature signatureWithObjCTypes: "@^v^c"];

Recall that observables should be able to simply send an arbitrary observer selector to their observers. This means we may run into a situation where no observer implements the given selector. Usually this would cause the runtime to throw an exception, because an unrecognized selector is sent to an instance.

The key to resolving this situation without running into runtime errors is to provide an arbitrary method signature. That is, we just simply ignore that we have no clue about the selector’s designated method signature by returning a default signature. This is possible, because we just want to swallow the invocation later on, i.e. there is no invocation in response to the message being sent to the observers instance.

Finally, this is what the corresponding forwardInvocation: implementation looks like:

- (void)forwardInvocation:(NSInvocation*)anInvocation
{
    for(id observer in observers) {
        if([observer respondsToSelector:[anInvocation selector]]) {
            [anInvocation invokeWithTarget:observer];
        }
    }
}

This code checks whether there is a registered observer that responds to the given invocation’s selector. If there is one, we can be sure that methodSignatureForSelector: did successfully respond with the correct method signature for the required selector. If not, we simply do nothing. Thus, it is completely irrelevant whether the previously requested method signature was correct or not.

I found one single blog post about Elegant Delegation that explicitly discusses this issue. There is also a post by Matt Gallagher about collecting arbitrary messages using NSInvocation. While that post is great, the method provided there appears overly complex for the problem at hand in this case.

Sample Code

Download Observer Sample Code (22 KB)

Tags: ·

10 responses so far ↓

  • 1 A Delegate-like Observer Mechanism in Objective-C using Forward … | effort.ly  Sep 23, 2011 at 9:25 am

    […] article: A Delegate-like Observer Mechanism in Objective-C using Forward … /* */ /* */ window.fbAsyncInit = function() { […]

  • 2 bob  Feb 2, 2012 at 3:44 am

    This is not true: “it is likely to cause compiler warnings due to the fact that observers are of type id”

    The point of id is that it does not check whether a method is supported

    You might get a separate compiler warning that the selectors were not declared — the compiler must know the signature of a selector in order to compile a call to it correctly. That’s why you implemented a protocol — simply to provide a declaration to the compiler. You do not actually need to implement this protocol.

  • 3 Tobias  Feb 21, 2012 at 2:57 pm

    You are right. Bad wording. I’m gonna fix this.

  • 4 bob  Apr 24, 2012 at 4:14 am

    Your code has several memory leaks.

    * You set “observers = [[NSMutableArray mutableArrayUsingWeakReferences] retain];”. But “[NSMutableArray mutableArrayUsingWeakReferences]” already returns a retained object.

    * In ContactManager.m, you pass “[name copy]” as the argument in several places. But “copy” returns a retained object.

  • 5 Tobias  Apr 27, 2012 at 9:38 pm

    You are right. This needs to be fixed. The first issue, however, is rooted in NSMutableArray(WeakReferences). The +mutableArrayUsingWeakReferences method needs to return an autoreleased object.

  • 6 umershahzad  Jun 12, 2012 at 2:57 pm

    what is delegate? plz tell me

  • 7 Tobias  Jun 13, 2012 at 7:00 pm

    Dear umershahzad,

    before I answer your question, please note that I have removed two repetitive posts of your question.

    Now let me provide a short answer: in Objective-C, a delegate is an object that receives messages from another object to act on behalf or in coordination with that object. The idea is that you provide a delegate to a (framework) class which then simply calls methods of that delegate so as to give you a chance to perform certain tasks when an event occurs. This is one way to implement “inversion of responsibility” without having to create a subclass of a framework class. It eases the process of writing reusable software components a lot.

    A complete discussion in the context of Objective-C is provided in the Apple Developer Documentation.

    A more general article about the delegation pattern can be retrieved from Wikipedia: http://en.wikipedia.org/wiki/Delegation_pattern

    Cheers, Tobias

  • 8 voyeur  May 3, 2013 at 2:19 am

    Hello just wanted to give you a quick heads up. The words in your article seem to
    be running off the screen in Ie. I’m not sure if this is a format issue or something to do with internet browser compatibility but I figured I’d post to let you know.
    The style and design look great though! Hope you get the
    issue solved soon. Thanks

  • 9 Tobias  May 3, 2013 at 7:03 am

    @voyeur: Thanks for the info, please switch to a modern browser!

  • 10 Annica Löv  May 16, 2014 at 3:34 pm

    Tobias, are you as experienced in PHP? Great reading btw, contact me.

Leave a Comment

Before you comment: please be kind and save me some time by NOT posting inappropriate content. Example: if you're religious that's fine. But it doesn't belong here! Comments should generally be related to the topic of the article. Thanks!

© 2013 Tobis Lensing. All rights reserved. Powered by Wordpress — based upon Cutline by Chris Pearson. This page reflects the personal opinion of the author and is in no way linked to institutions the author is working or has worked for. For more information, see the disclaimer.