1

I have a view controller which defines a protocol which itself inherits another protocol. I want any object that implements my protocol to also implement the inherited protocol.

I want to set my class to intercept some of the messages in the inherited protocol in order to configure some things internally but eventually would like to forward all of the messages to the delegate of my class

I could write a lot of boiler plate code to stub all of the protocol and intern call the delegate but I see that it breaks a lot of the time - any time the "super" protocol changes I need to restub this class once again.

I see that this is very predominant in custom UI controls. When reusing existing components - for instance tables or collection views you would like your data source to respond to all of the common protocols but some instances you need to configure the view according to the index or save a particular state.

I've tried using forwardingTargetForSelector in order to forward the messages I do not respond to , but it isn't always forwarding...

Here is a contrived code example:

Class A: (the top most protocol)

@

    protocol classAProtocol <NSObject>

    -(void)method1;
    -(void)method2;
    -(void)method3;

    @end

    My Class

    @protocol MyClassProtocol <classAProtocol>

    -(void)method4;
    @end

    @interface MyClass

    @property (nonatomic,weak> id <MyClassProtocol> delegate;

    @end

    @interface MyClass (privateInterface)

    @property (nonatomic,strong) ClassA *classAObject;
    @end
    @implementation MyClass

    -(init)
    {
      self = [super init];
      if (self)
      {
        _classAObject = [[ClassA alloc] init];
        _classAObject.delegate = self; // want to answer some of the delegate methods but not all
      }
    }

-(void)method1
{
  // do some internal configuration
  // call my delegate with
  [self.delegate method1];
}
    -(id)forwardingTargetForSelector:(SEL)aSelector
    {
        if ([self respondsToSelector:aSelector])
        {
            return self;
        }
        if ([self.delegate respondsToSelector:aSelector])
        {
            return self.delegate;
        }
        return nil;
    }

    -(void)setDelegate:(id <MyClassProtocol>)delegate
    {
      self.delegate = delegate; // will forward some of the messages
    }
    @end
Avba
  • 14,822
  • 20
  • 92
  • 192

2 Answers2

3

Returning self from forwardingTargetForSelector: makes no sense because it would never be called if self responded to the selector.

You need to implement these three methods:

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    if ([self.delegate respondsToSelector:[anInvocation selector]])
        [anInvocation invokeWithTarget:self.delegate];
    else
        [super forwardInvocation:anInvocation];
}

- (BOOL)respondsToSelector:(SEL)aSelector
{
    return [super respondsToSelector:aSelector] || [self.delegate respondsToSelector:aSelector];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
    NSMethodSignature *signature = [super methodSignatureForSelector:selector];
    if (!signature) {
        signature = [self.delegate methodSignatureForSelector:selector];
    }
    return signature;
}
atomkirk
  • 3,701
  • 27
  • 30
  • Added those implementations. Not sure what the 3rd method does (methodSignatureForSelector). The selector should be the same in the delegate shouldn't it? Also if the delegate doesn't implement that method calling [self.delegate methodSignatureForSelector:selector]; doesn't compile – Avba Aug 22 '13 at 08:20
  • It's used by the system when constructing an NSInvocation. This will explain everything behind the code I provided: https://developer.apple.com/library/ios/DOCUMENTATION/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtForwarding.html – atomkirk Aug 22 '13 at 19:07
0

You shouldn't ever return self; from forwardingTargetForSelector:. Your check should mean it never is but if you ever did return self it would cause an infinite loop.

You need to be sure that a super class isn't implementing the method as this will prevent forwardingTargetForSelector: from being called. Check if the method is actually called.

forwardingTargetForSelector: is also only called when a method is called on your controller that it doesn't respond to. In your example you aren't calling [self ...];, you're calling [self.delegate ...]; so forwardingTargetForSelector: will not be called.

Wain
  • 118,658
  • 15
  • 128
  • 151
  • Not clear on what your saying. If I say in my class self.someObject.delegate = self; And not implement its delegate will the message not be forwarded to my delegate which is expected to implement it? – Avba Aug 22 '13 at 08:19
  • Methods won't be forwarded to a different object unless you write some code to make it happen. If you call `self` and don't implement the method (and a super class doesn't implement it) then your `forwardingTargetForSelector` will forward it. – Wain Aug 22 '13 at 09:46