2008-11-24 10 views
133

¿Cómo funciona exactamente NSInvocation? ¿Hay una buena introducción?NSInvocación para Dummies?

Estoy específicamente teniendo problemas para entender cómo funciona el siguiente código (desde Cocoa Programming para Mac OS X, 3ra Edición), pero también puede aplicar los conceptos independientemente del ejemplo del tutorial. El código:

- (void)insertObject:(Person *)p inEmployeesAtIndex:(int)index 
{ 
    NSLog(@"adding %@ to %@", p, employees); 
    // Add inverse of this operation to undo stack 
    NSUndoManager *undo = [self undoManager]; 
    [[undo prepareWithInvocationTarget:self] removeObjectFromEmployeesAtIndex:index]; 
    if (![undo isUndoing]) 
     [undo setActionName:@"Insert Person"]; 

    // Finally, add person to the array 
    [employees insertObject:p atIndex:index]; 
} 

- (void)removeObjectFromEmployeesAtIndex:(int)index 
{ 
    Person *p = [employees objectAtIndex:index]; 
    NSLog(@"removing %@ from %@", p, employees); 
    // Add inverse of this operation to undo stack 
    NSUndoManager *undo = [self undoManager]; 
    [[undo prepareWithInvocationTarget:self] insertObject:p 
             inEmployeesAtIndex:index]; 
    if (![undo isUndoing]) 
     [undo setActionName:@"Delete Person"]; 

    // Finally, remove person from array 
    [employees removeObjectAtIndex:index]; 
} 

Entiendo lo que está tratando de hacer. (Por cierto, es un employeesNSArray de una clase personalizada Person.)

ser un tipo .NET, trato de asociar conceptos desconocidos Obj-C y Cocoa de conceptos de .NET más o menos análogos. ¿Es esto similar al concepto de delegado de .NET, pero sin tipo?

Esto no está 100% claro del libro, entonces estoy buscando algo suplementario de expertos reales de Cocoa/Obj-C, una vez más con el objetivo de entender el concepto fundamental debajo del ejemplo simple (-ish) . Realmente estoy buscando poder aplicar el conocimiento de forma independiente - hasta el capítulo 9, no tuve ninguna dificultad para hacerlo. Pero ahora ...

¡Gracias de antemano!

Respuesta

269

Según Apple's NSInvocation class reference:

Un NSInvocation es un mensaje de Objective-C rindió estático, es decir, que es una acción se convirtió en un objeto.

Y, en un poco más detalle:

El concepto de mensajes es central en la filosofía de Objective-C. Cada vez que llamas a un método o accedes a una variable de algún objeto, le estás enviando un mensaje. NSInvocation es útil cuando desea enviar un mensaje a un objeto en un momento diferente, o envía el mismo mensaje varias veces. NSInvocation le permite describir el mensaje que va a enviar, y luego invocar (en realidad enviarlo al objeto de destino) más adelante.


Por ejemplo, supongamos que desea agregar una cadena a una matriz.Que normalmente enviar el mensaje addObject: de la siguiente manera:

[myArray addObject:myString]; 

Ahora, supongamos que desea utilizar NSInvocation para enviar este mensaje en algún otro punto en el tiempo:

En primer lugar, debería preparar una NSInvocation objeto para su uso con NSMutableArray 's addObject: selector:

NSMethodSignature * mySignature = [NSMutableArray 
    instanceMethodSignatureForSelector:@selector(addObject:)]; 
NSInvocation * myInvocation = [NSInvocation 
    invocationWithMethodSignature:mySignature]; 

a continuación, le SPE cify qué objeto para enviar el mensaje a:

[myInvocation setTarget:myArray]; 

especificar el mensaje que desea enviar a ese objeto:

[myInvocation setSelector:@selector(addObject:)]; 

Y rellenar los argumentos para ese método:

[myInvocation setArgument:&myString atIndex:2]; 

Tenga en cuenta que los argumentos del objeto deben pasarse por puntero. Gracias a Ryan McCuaig por señalarlo, y por favor vea Apple's documentation para más detalles.

En este punto, myInvocation es un objeto completo, que describe un mensaje que se puede enviar. Para enviar realmente el mensaje, que podría llamarse:

[myInvocation invoke]; 

Este último paso hará que el mensaje sea enviado, esencialmente ejecutar [myArray addObject:myString];.

Piénsalo como enviar un correo electrónico. Abre un nuevo correo electrónico (NSInvocation objeto), rellene la dirección de la persona (objeto) a quien desea enviarlo, escriba un mensaje para el destinatario (especifique un selector y argumentos), y luego haga clic en "enviar" (llame al invoke).

Consulte Using NSInvocation para obtener más información. Consulte Using NSInvocation si lo anterior no funciona.


NSUndoManager utiliza NSInvocation objetos para que se pueda revertir comandos. Esencialmente, lo que estás haciendo es crear un objeto NSInvocation para decir: "Oye, si quieres deshacer lo que acabo de hacer, envía este mensaje a ese objeto, con estos argumentos". Le concede el objeto NSInvocation al NSUndoManager, y agrega ese objeto a una matriz de acciones anulables. Si el usuario llama "Deshacer", NSUndoManager simplemente busca la acción más reciente en la matriz e invoca el objeto NSInvocation almacenado para realizar la acción necesaria.

Ver Registering Undo Operations para más detalles.

+9

Una pequeña corrección a una por lo demás excelente respuesta ... usted tiene que pasar un puntero a objetos en 'setArgument: atIndex:', por lo que el arg la tarea debería leer '[myInvocation setArgument: & myString atIndex: 2]'. –

+0

@Ryan McCuaig: Gracias por señalar eso. Realicé el cambio y agregué un enlace a la documentación relevante. –

+56

Solo para aclarar la nota de Ryan, el índice 0 está reservado para "self" y el índice 1 está reservado para "_cmd" (consulte el enlace e.James publicado para obtener más detalles). Entonces su primer argumento se coloca en el índice 2, el segundo argumento en el índice 3, etc. ... –

45

Aquí está un ejemplo sencillo de NSInvocation en acción:

- (void)hello:(NSString *)hello world:(NSString *)world 
{ 
    NSLog(@"%@ %@!", hello, world); 

    NSMethodSignature *signature = [self methodSignatureForSelector:_cmd]; 
    NSInvocation  *invocation = [NSInvocation invocationWithMethodSignature:signature]; 

    [invocation setTarget:self];     // index 0 (hidden) 
    [invocation setSelector:_cmd];     // index 1 (hidden) 
    [invocation setArgument:&hello atIndex:2];  // index 2 
    [invocation setArgument:&world atIndex:3];  // index 3 

    // NSTimer's always retain invocation arguments due to their firing delay. Release will occur when the timer invalidates itself. 
    [NSTimer scheduledTimerWithTimeInterval:1 invocation:invocation repeats:NO]; 
} 

Cuando se llama - [self hello:@"Hello" world:@"world"]; - el método será:

  • Imprimir "¡Hola, mundo!"
  • Crea un NSMethodSignature por sí mismo.
  • Crea y completa una invocación NS, llamándose a sí mismo.
  • Pase la Invocación NS a un NSTimer
  • El temporizador se activará en (aproximadamente) 1 segundo, haciendo que el método se vuelva a llamar con sus argumentos originales.
  • Repita.

Al final, obtendrá una copia impresa de este modo:

2010-07-11 17:48:45.262 Your App[2523:a0f] Hello world! 
2010-07-11 17:48:46.266 Your App[2523:a0f] Hello world! 
2010-07-11 17:48:47.266 Your App[2523:a0f] Hello world! 
2010-07-11 17:48:48.267 Your App[2523:a0f] Hello world! 
2010-07-11 17:48:49.268 Your App[2523:a0f] Hello world! 
2010-07-11 17:48:50.268 Your App[2523:a0f] Hello world! 
2010-07-11 17:48:51.269 Your App[2523:a0f] Hello world! 
... 

Por supuesto, el objeto de destino self debe seguir existiendo en el NSTimer para enviar el NSInvocation a ella. Por ejemplo, un objeto Singleton, o un AppDelegate que existe durante la aplicación.


ACTUALIZACIÓN:

Como se señaló anteriormente, cuando se pasa un NSInvocation como argumento a una NSTimer, la NSTimer retiene automáticamente todos los argumentos de la NSInvocation.

Si no está pasando una Invocación NS como argumento a un NSTimer, y planea que se quede por un tiempo, debe llamar a su método -retainArguments. De lo contrario, sus argumentos pueden desasignarse antes de que se invoque la invocación, lo que eventualmente hará que su código falle. He aquí cómo hacerlo:

NSMethodSignature *signature = ...; 
NSInvocation  *invocation = [NSInvocation invocationWithMethodSignature:signature]; 
id    arg1  = ...; 
id    arg2  = ...; 

[invocation setTarget:...]; 
[invocation setSelector:...]; 
[invocation setArgument:&arg1 atIndex:2]; 
[invocation setArgument:&arg2 atIndex:3]; 

[invocation retainArguments]; // If you do not call this, arg1 and arg2 might be deallocated. 

[self someMethodThatInvokesYourInvocationEventually:invocation]; 
+6

Es interesante que, aunque se utiliza el inicializador 'invocationWithMethodSignature:', aún necesita llamar a 'setSelector:'. Parece redundante, pero Acabo de probar y es necesario. – ThomasW

+0

¿Se puede hacer con métodos estáticos? – Apollo

+0

¿Esto sigue funcionando en un bucle infinito? Y ¿qué es _cmd? – j2emanue

Cuestiones relacionadas