NSInvocation
En entornos orientados a objetos muy dinámicos, como es el caso de Cocoa & Objective C o Smalltalk, los mensajes entre objetos están siendo continuamente enviados, redireccionados, guardados para uso posterior, reutilizados y distribuidos a otros objetos. Nada de esto es posible en entornos más estáticos, como pueda ser C++.
La pieza fundamental que permite toda esta flexibilidad es NSInvocation, clase que representa el procedimiento de “enviar un mensaje a alguien”.
Qué es NSInvocation
Si como un servidor, ya peinas canas, seguramente recuerdas el antiguo procedimiento para enviar un mensaje a alguien:
- Coges un objeto llamado sobre.
- Guardas tu mensaje dentro de él.
- Añades el destinatario
- Lo depositas en un objeto metálico situado en la calle y llamado “buzón de correos”.
Selector + Method Signature = Mensaje
- El “selector”
- La “message signature”
- A acepta un id como parámetro y devuelve un puntero a A
- B acepta una NSString y devuelve un puntero a B.
Crear y configurar una instancia de NSInvocation
- Crear un NSMethodSignature tal como se describe en el párrafo anterior
- Crear una instancia de NSInvocation pasándole la NSMethodSignature (primera parte del mensaje).
- Indicarle al NSInvocation qué selector habrá que usar (segunda parte y última parte del mensaje: ya tiene el mensaje completo).
- Indicar a NSInvocation el destinatario del mensaje.
- Indicar a NSInvocation los parámetros del mensaje, uno a uno. Ten en cuenta que todo mensaje en Cocoa tiene dos argumentos ocultos (self y _cmd), así que los tuyo empiezan en el índice 2. OJO, que los parámetros se pasan por referencia.
Enviar el mensaje representado por NSInvocation
Uso de NSInvocation
- Implementar el “deshacer” en documentos. Cuando se va a hacer un cambio a un documento, se crea una NSInvocation con el efecto contrario. Si hay que deshacer, simplemente se invoca a esa NSInvocation.
- Redireccionamiento de mensajes o “message forwarding“. Cuando un objeto recibe un mensaje que no entiende y ya ha recurrido a sus superclases y ninguna de ellas tampoco lo entiende, queda un último recurso: redireccionarlo a alguien que pueda entenderlo. Para ello se “empaqueta” el mensaje en un NSInvocation y se envía a sí mismo como el parámetro del mensaje forwardInvocation: Tu puedes sobrescribir dicho método y especificar a quién mandar el mensaje problemático. Si esto tampoco funciona, se genera una excepción informando que el objeto no responde al mensaje problemático.
Guardar una NSInvocation para uso posterior
Ejemplo de NSInvocation
// Destinatario del mensaje y argumentos del mismo NSString *planeta = @"http://cocoaosx.com/2010/11/30/planeta-cocoa/"; NSString *bloque = @"http://cocoaosx.com/2011/06/22/como-retrasar-la-ejecucion-de-un-bloque-en-objective-c/"; NSString *res; int option = NSCaseInsensitiveSearch; // NSInvocation: el "sobre" en el que meteremos el mensaje NSInvocation *env = [NSInvocation invocationWithMethodSignature: [planeta methodSignatureForSelector: @selector(commonPrefixWithString:options:)]]; [env setTarget:planeta]; [env setSelector:@selector(commonPrefixWithString:options:)]; // Los argumentos se pasan por referencia [env setArgument:&bloque atIndex:2]; [env setArgument:&option atIndex:3]; // Como pienso guardar esta instancia de NSInvocation // para usarla más tarde, tengo que retener los argumentos // ya que NSinvocation no lo hace por defecto. [env retainArguments]; // Enviamos el sobre. [env invoke]; // Recuperamos el valor de retorno. OJO que se hace por referencia. // Como el método empieza por "get" significa que somos reponsables // por liberarlo (release). Ver las 3 reglas de oro de la // gestión de memoria en Objective C: // http://cocoaosx.com/2010/11/20/las-3-reglas-de-oro-de-la-gestion-de-memoria-en-cocoa/ [env getReturnValue:&res]; NSLog(@"%@", res); [res release];
Conclusiones
Aunque el ejemplo en sí no tiene demasiado sentido, sirve como receta de cómo crear y usar una NSInvocation. En otros artículos veremos ejemplos realistas de su uso. Especialmente en el caso de “message forwarding”, con el que se pueden hacer verdaderas virguerías (como “Higher Order Messaging”, DSLs y demás) así como cagadas portentosas.