10

I have two classes that can act as a delegate of a third class, and both implement a formal protocol made entirely of optional methods. One of the classes implements everything while another only implements a couple methods that i care about. However, at runtime when i have the second class act as the delegate to the third class, and the third class ends up calling one of the unimplemented optional methods on that delegate, i get a runtime error essentially saying "Target does not respond to this message selector." I thought that objective-c handled this case correctly, and that it would just do nothing if that method wasn't actually defined on the class. Might there be something i'm missing?

Kevlar
  • 8,804
  • 9
  • 55
  • 81

4 Answers4

34

When you call an optional method of your delegate, you need to make sure it responds to the selector before calling it:

if ([delegate respondsToSelector:@selector(optionalMethod)])
    [delegate optionalMethod];
Ben Gotow
  • 14,805
  • 3
  • 42
  • 47
  • 2
    I suspected as much, but i was hoping i wouldn't need to add those if-checks all over the code. Thanks for the pointer. – Kevlar Jun 22 '09 at 19:29
10

Optional protocol methods simply mean the object implementing the protocol does not have to implement the method in question - the callee then absolutely must check whether the object implements the method before calling (otherwise you'll crash, as you noticed). These NSObject HOM categories can be helpful:

@implementation NSObject (Extensions)

- (id)performSelectorIfResponds:(SEL)aSelector
{
    if ( [self respondsToSelector:aSelector] ) {
        return [self performSelector:aSelector];
    }
    return NULL;
}

- (id)performSelectorIfResponds:(SEL)aSelector withObject:(id)anObject
{
    if ( [self respondsToSelector:aSelector] ) {
        return [self performSelector:aSelector withObject:anObject];
    }
    return NULL;
}

@end

Then you can simply do:

[delegate performSelectorIfResponds:@selector(optionalMethod)];
Peter N Lewis
  • 17,664
  • 2
  • 43
  • 56
4

This Blocks solution works well, once you get your head wrapped around what is going on. I added a BOOL result because I wanted to be able to conditionally run one of several optional methods. Some tips if you are trying to implement this solution:

First, if you haven't encountered Extension/Categories yet, you simply add this to the top of your class, OUTSIDE the existing class definition. It will be a public or private extension based on where you put it.

@implementation NSObject (Extensions)
// add block-based execution of optional protocol messages
-(BOOL) performBlock:(void (^)(void))block ifRespondsTo:(SEL) aSelector
{
    if ([self respondsToSelector:aSelector]) {
        block();
        return YES;
    }
    return NO;
}
@end

Second, here's how you call it from your code:

BOOL b = [(NSObject*)self.delegate performBlock:^{
    // code to run if the protocol method is implemented
} 
ifRespondsTo:@selector(Param1:Param2:ParamN:)];

Replace Param1:Param2:ParamN: with the names of each parameter for your protocol method. Each one should end with a colon. So if your protocol method looks like:

-(void)dosomething:(id)blah withObj:(id)blah2 andWithObj(id)blah3;

the last line would look like this:

ifRespondsTo:@selector(dosomething:withObj:andWithObj:)];

Eli Burke
  • 2,729
  • 27
  • 25
2

Blocks might provide a better solution. They allow to conditionally perform any code based on the existence of an implementation of a given method:

-(void) performBlock:(void (^)(void))block ifRespondsTo:(SEL) aSelector{
    if ([self respondsToSelector:aSelector]) {
        block();
    }
}

By using this addition to NSObject, you can conditionally execute any @optional method, no matter how many parameters it might have.

See How to safely send @optional protocol messages that might not be implemented

cfischer
  • 24,452
  • 37
  • 131
  • 214