Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do unimplemented optional protocol methods cause runtime errors when that method is called in obj-c?

Tags:

objective-c

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?

like image 900
Kevlar Avatar asked Jun 22 '09 19:06

Kevlar


4 Answers

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

like image 74
cfischer Avatar answered Sep 23 '22 04:09

cfischer


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];
like image 38
Ben Gotow Avatar answered Sep 20 '22 04:09

Ben Gotow


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)];
like image 39
Peter N Lewis Avatar answered Sep 22 '22 04:09

Peter N Lewis


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:)];

like image 40
Eli Burke Avatar answered Sep 21 '22 04:09

Eli Burke