@optional methods in Objective C protocols

Objective C 2.0 added support for @optional methods in protocols. Whatever class conforms to the protocol, it won’t be forced to implement “empty” methods if it doesn’t need them. This is certainly handy for the client conforming to the protocol. Unfortunately, it makes life miserable on the other side.

Handling non implemented @optional methods in Cocoa

Assume you have a class called  SimpleStateMachine that defines the following delegate protocol:

The delegate is free to implement the methods that are necessary and ignore those that aren’t. However, the class SimpleStateMachine cannot blindly send those messages to its delegate, as if a single one is not implemented, the application will crash.

I was expecting the runtime to detect if an @optional method is not implemented, and sending it to nil. Not so.

Code, toil and tears

Since there aint such thing as free lunch, what are you options? It seems like you must manually check respondsToSelector: before sending the message every single time. This is not only tedious, but substantially uglifies your code.

A better option would be to define a HOM method in a category on NSObject, as suggested by Peter N. Lewis in StackOverflow:

You will soon need a withObject: version:

and maybe a withObject:withObject: one.

A slightly more elegant and far more flexible solution: enter Blocks

Blocks allow to conditionally perform any code based on the existence of an implementation of a given method:

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

Your code might look like this:

I’m still looking for a less verbose solution, but this seems reasonably satisfactory by now.

 

Tagged with:  

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

  1. Emmanuel Gomez says:

    Pardon me for being blunt, but in what way is your final, block-based solution superior to the original?

    The original is (effectively):

    if ([self.delegate respondsToSelector:@selector(aSelector)])
    [self.delegate aSelector];

    Your block based solution is more code:

    [(NSObject*)self.delegate performBlock:^{
    [self.delegate simpleStateMachineWillMoveFromState:self._currentState toState:newState];
    } ifRespondsTo:@selector(simpleStateMachineWillMoveFromState:toState:)];

    I think the original is better. I agree that performSelectorIfResponds: is a little ungainly but at least it only requires typing the method name once.

  2. Narciso Cerezo says:

    Hi Fernando,
    Thanks for this approach, I find the block solution the most elegant. However, if the selector returns a value that you must use in your flow, or you must take action if the selector is not implemented, if falls a bit short.
    This might not be a perfect solution either, but I think it is a good addition for such a case:

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

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

    This way you could write something like:

    [(NSObject*)self.delegate performBlock:^{
    [self.delegate simpleStateMachineWillMoveFromState:self._currentState toState:newState];
    } ifRespondsTo:@selector(simpleStateMachineWillMoveFromState:toState:) elsePerformBlock:^{
    // do something
    }];

    Cheers!

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">