2

I came across this code in a project, where it is using NSInvocation. I want to know what it is supposed to do, and why would we ever need that. Simple explanations would be appreciated. I am posting the code.

// Public interface
@interface CCDelegateSplitter : NSObject

- (void) addDelegate: (id) delegate;
- (void) addDelegates: (NSArray*) delegates;

@end

// Private interface
@interface CCDelegateSplitter ()
@property(strong) NSMutableSet *delegates;
@end

@implementation CCDelegateSplitter

- (id) init
{
    self = [super init];
    _delegates = [NSMutableSet set];
    return self;
}

- (void) addDelegate: (id) delegate
{
    [_delegates addObject:delegate];
}

- (void) addDelegates: (NSArray*) delegates
{
    [_delegates addObjectsFromArray:delegates];
}

- (void) forwardInvocation: (NSInvocation*) invocation
{
    for (id delegate in _delegates) {
        [invocation invokeWithTarget:delegate];
    }
}

- (NSMethodSignature*) methodSignatureForSelector: (SEL) selector
{
    NSMethodSignature *our = [super methodSignatureForSelector:selector];
    NSMethodSignature *delegated = [(NSObject *)[_delegates anyObject] methodSignatureForSelector:selector];
    return our ? our : delegated;
}

- (BOOL) respondsToSelector: (SEL) selector
{
    return [[_delegates anyObject] respondsToSelector:selector];
}

@end
Kumar Utsav
  • 2,761
  • 4
  • 24
  • 38
  • 1
    [Check this answer out.](http://stackoverflow.com/questions/313400/nsinvocation-for-dummies) Pretty much tells you everything you need to know :) – bfich Dec 23 '15 at 17:43

2 Answers2

5

I'll assume you know what an NSInvocation is (if not, it's a data structure that holds all the information needed to make a method call; think "blocks" from long before blocks were added to the language).

forwardInvocation: is one of the methods that the runtime will call if it cannot find an implementation for a method. So if you pass a -doSomething message to an object [object doSomething], it will first check whether it has a doSomething method. Then it will check its superclasses. It'll try dynamic method resolution (resolveInstanceMethod for instance). It'll look for a forwarding target (forwardingTargetForSelector:), and it'll finally, if everything else fails, it'll create an invocation (using methodSignatureForSelector: and punt to forwardInvocation:. By default, forwardInvocation: just calls doesNotRecognizeSelector: which crashes you on iOS (or terminates the current thread on OS X). But you can override it to do something else (like they have here).

methodSignatureForSelector: is necessary so that the runtime system can create an invocation out of a message. This one either returns a method signature from this object or its superclasses, or it asks one of its targets for the appropriate method signature. A selector by itself isn't enough to figure out exactly how to call a method. The system needs to ask an object how the method actually works (what types it takes and what type it returns).

The code you've posted is a multi-delegate trampoline. It will accept any selector that its targets respond to (technically it'll pick a random target and see if it responds), and it'll forward that message to all of its targets.

For a similar trampoline with some comments about usage, you may want to look at RNObserverManager.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • Good answer, but from the question I assumed that the OP did **not** know what an `NSInvocation` is. – Duncan C Dec 23 '15 at 18:02
3

Take a look at the link in bfitch's comment. That covers what NSInvocation is, and hints at why you would use it, but doesn't cover the why in much detail.

NSInvocation lets you do fairly advanced things like creating a proxy object that actually forwards messages to another object. With NSInvocation it's possible to take ANY message at runtime and forward it to another object.

Another example is the performSelector family of methods. There's performSelector:, performSelector:withObject:, and performSelector:withObject:withObject:. (plus variants like performSelector:withObject:afterDelay:, performSelector:onThread:, etc.) Those methods take 0, 1, or or 2 objects as parameters. If you need to invoke a method on another object that takes scalar parameters, or anything other than 0, 1 or 2 objects, you're out of luck. However you can send messages with ANY type of parameters using NSInvocation.

Note that when blocks were added to Objective-C the need for tricks like performSelector and NSInvocation become less. Blocks can reference variables from their enclosing scope, which makes them more flexible.

Duncan C
  • 128,072
  • 22
  • 173
  • 272