Delegate Multiplexing

I have mentioned before how Cocoa is biased towards the delegate pattern. I argued that in some instances the observer pattern would be better suited. But in some cases the delegate pattern is the way to go.

Something you could argue as a limitation of the delegate pattern is the fact that there can only be one delegate. This criticism is not entirely valid because if you need more than one maybe you shouldn't be using the delegate pattern in the first place. But sometimes you are stuck with the delegate approach because that is how the a third-party framework (e.g. Cocoa) was designed.

Here is a workaround for this limitation. The idea is to have a central delegate that dispatches messages to multiple delegates. I call this delegate multiplexing.

We need to have an object that implements forwardInvocation: to forward any unknown selectors to its sub-delegates. If you override forwardInvocation: you also need to override methodSignatureForSelector: because that is how the invocation is constructed. So let's start with that. We need to get the method signature from the first sub-delegate that implements the selector:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
    NSMethodSignature* signature =
        [super methodSignatureForSelector:selector];
    if (signature)
        return signature;

    for (id delegate in _delegates) {
        signature = [delegate methodSignatureForSelector:selector];
        if (signature)
            break;
    }

    return signature;
}

Next we implement forwardInvocation: by just forwarding the invocation to any sub-delegates that respond to the selector:

- (void)forwardInvocation:(NSInvocation *)invocation {
    SEL selector = [invocation selector];
    BOOL responded = NO;

    for (id delegate in _delegates) {
        if ([delegate respondsToSelector:selector]) {
            [invocation invokeWithTarget:delegate];
            responded = YES;
        }
    }

    if (!responded)
        [self doesNotRecognizeSelector:selector];
}

An interesting side-effect of this is that the return value of the method invocation will be the return value of the last sub-delegate that responded to the selector. This is because the NSInvocation object stores the return value of invokeWithTarget:, and it's replaced every time a new invocation is made.

The other method we need to implement is respondsToSelector:. Here we want to return YES if any of the sub-delegates responds to the selector:

- (BOOL)respondsToSelector:(SEL)selector {
    if ([super respondsToSelector:selector])
        return YES;

    for (id delegate in _delegates) {
        if ([delegate respondsToSelector:selector])
            return YES;
    }

    return NO;
}

You also need to add methods to handle adding and removing sub-delegates. That's it!

Because of how we implemented respondsToSelector: it's better to first add all the sub-delegates and then set the delegate property of the target object. Some objects only call respondsToSelector: when you first set the delegate to improve performance.

See the full code with tests in the MultiDelegate GitHub project page. You can also just add it to your project via CocoaPods with pod 'MultiDelegate'.