NSInvocación para Dummies?

139

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

Tengo problemas específicos para comprender cómo funciona el siguiente código (de Cocoa Programming para Mac OS X, 3a edición ), pero también puedo 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, employeeses NSArrayde una Personclase personalizada ).

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

Esto no está 100% claro en el libro, por lo que estoy buscando algo complementario de verdaderos expertos de Cocoa / Obj-C, nuevamente con el objetivo de que entiendo 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 dificultades para hacerlo. Pero ahora ...

¡Gracias por adelantado!

John Rudy
fuente

Respuestas:

284

De acuerdo con la referencia de clase NSInvocation de Apple :

Un NSInvocationes un mensaje de Objective-C convertido en estático, es decir, es una acción convertida en un objeto.

Y, con un poco más de detalle:

El concepto de mensajes es fundamental para la filosofía del objetivo-c. Cada vez que llama a un método o accede a una variable de algún objeto, le está enviando un mensaje. NSInvocationes útil cuando desea enviar un mensaje a un objeto en un momento diferente o enviar el mismo mensaje varias veces. NSInvocationle permite describir el mensaje que va a enviar y luego invocarlo (en realidad, enviarlo al objeto de destino) más adelante.


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

[myArray addObject:myString];

Ahora, supongamos que desea utilizar NSInvocationpara enviar este mensaje en otro momento:

Primero, prepararía un NSInvocationobjeto para usar con NSMutableArrayel addObject:selector de:

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

A continuación, especificaría a qué objeto enviar el mensaje:

[myInvocation setTarget:myArray];

Especifique el mensaje que desea enviar a ese objeto:

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

Y complete cualquier argumento para ese método:

[myInvocation setArgument:&myString atIndex:2];

Tenga en cuenta que los argumentos de los objetos se deben pasar por puntero. Gracias a Ryan McCuaig por señalarlo, y consulte la documentación de Apple para obtener más detalles.

En este punto, myInvocationes un objeto completo, que describe un mensaje que se puede enviar. Para enviar el mensaje, debe llamar a:

[myInvocation invoke];

Este último paso hará que se envíe el mensaje, esencialmente ejecutándose [myArray addObject:myString];.

Piense en ello como enviar un correo electrónico. Abre un nuevo correo electrónico ( NSInvocationobjeto), completa la dirección de la persona (objeto) a quien desea enviarlo, escribe un mensaje para el destinatario (especifique ay selectorargumentos) y luego haga clic en "enviar" (llamar invoke)

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


NSUndoManagerusa NSInvocationobjetos para que pueda revertir comandos. Esencialmente, lo que está haciendo es crear un NSInvocationobjeto que diga: "Oye, si quieres deshacer lo que acabo de hacer, envía este mensaje a ese objeto, con estos argumentos". Le da el NSInvocationobjeto a NSUndoManager, y agrega ese objeto a una matriz de acciones que no se pueden deshacer. Si el usuario llama "Deshacer", NSUndoManagersimplemente busca la acción más reciente en la matriz e invoca el NSInvocationobjeto almacenado para realizar la acción necesaria.

Consulte Registro de operaciones de deshacer para obtener más detalles.

e.James
fuente
10
Una corrección menor a una respuesta de otra manera excelente ... tienes que pasar un puntero a los objetos setArgument:atIndex:, por lo que la asignación de argumentos debería leerse [myInvocation setArgument:&myString atIndex:2].
Ryan McCuaig el
6060
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 ...
Dave
44
@haroldcampbell: ¿a qué tenemos que llamar?
e.James
66
No entiendo por qué tenemos que llamar a setSelector, ya que ya especificamos el selector en mySignature.
Gleno
66
@Gleno: NSInvocation es bastante flexible. En realidad, puede configurar cualquier selector que coincida con la firma del método, por lo que no necesariamente tiene que usar el mismo selector que se utilizó para crear la firma del método. En este ejemplo, podría hacer setSelector con la misma facilidad: @selector (removeObject :), ya que comparten la misma firma de método.
e.James
48

Aquí hay un ejemplo simple 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:

  • Imprimir "¡Hola mundo!"
  • Cree una NSMethodSignature para sí mismo.
  • Cree y complete una NSInvocation, llamándose a sí mismo.
  • Pase la NSInvocation a un NSTimer
  • El temporizador se disparará en (aproximadamente) 1 segundo, haciendo que se llame nuevamente al método con sus argumentos originales.
  • Repetir.

Al final, obtendrás una copia impresa así:

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 selfdebe continuar existiendo para que el NSTimer le envíe la NSInvocation. Por ejemplo, un objeto Singleton o un AppDelegate que existe mientras dura la aplicación.


ACTUALIZAR:

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

Si no está pasando una NSInvocation como argumento a un NSTimer y planea que se quede por un tiempo, debe llamar a su -retainArgumentsmétodo. De lo contrario, sus argumentos pueden ser desasignados antes de que se invoque la invocación, lo que eventualmente ocasionará que su código se bloquee. Aquí se explica 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];
Dave
fuente
66
Es interesante que, aunque invocationWithMethodSignature:se use el inicializador, aún tenga que llamar setSelector:. Parece redundante, pero acabo de probar y es necesario.
ThomasW
¿Esto sigue funcionando en un bucle infinito? y lo que es _cmd
j2emanue