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

Autor: | Última modificación: 9 de noviembre de 2022 | Tiempo de Lectura: 2 minutos
Temas en este post:

@optional protocol messages in Objective C

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:
[crayon lang=»objective c»]
@protocol
@optional
-(void) simpleStateMachineWillMoveFromState:(NSString *) oldState
toState: (NSString *) newState;

-(void) simpleStateMachineDidMoveFromState:(NSString *) oldState
toState: (NSString *) newState;

-(BOOL) simpleStateMachineShouldMoveFromState:(NSString *) oldState
toState: (NSString *) newState;
@end
[/crayon]
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:
[crayon lang=»objective c»]
@implementation NSObject (Extensions)

– (id)performSelectorIfResponds:(SEL)aSelector
{
if ( [self respondsToSelector:aSelector] ) {
return [self performSelector:aSelector];
}
return NULL;
}[/crayon]
You will soon need a withObject: version:
[crayon lang=»objective c»]
– (id)performSelectorIfResponds:(SEL)aSelector withObject:(id)anObject
{
if ( [self respondsToSelector:aSelector] ) {
return [self performSelector:aSelector withObject:anObject];
}
return NULL;
}
[/crayon]
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:

[crayon lang=»objective c»]
-(void) performBlock:(void (^)(void))block ifRespondsTo:(SEL) aSelector{
if ([self respondsToSelector:aSelector]) {
block();
}
}
[/crayon]
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:
[crayon lang=»objective c»]
[(NSObject*)self.delegate performBlock:^{
[self.delegate simpleStateMachineWillMoveFromState:self._currentState toState:newState];
} ifRespondsTo:@selector(simpleStateMachineWillMoveFromState:toState:)];
[/crayon]
I’m still looking for a less verbose solution, but this seems reasonably satisfactory by now.

[email protected]

¿Trabajo? Aprende a programar y consíguelo.

¡No te pierdas la próxima edición del Aprende a Programar desde Cero Full Stack Jr. Bootcamp!

 

Prepárate en 4 meses, aprende las últimas tecnologías y consigue trabajo desde ya. 

 

Solo en España hay más de 120.400 puestos tech sin cubrir, y con un sueldo 11.000€ por encima de la media nacional. ¡Es tu momento!

 

🗓️ Próxima edición: 13 de febrero

 

Reserva tu plaza descubre las becas disponibles.