NSInvocación para Dummies?
-
10-07-2019 - |
Pregunta
¿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 for Mac OS X, 3rd Edition ), 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, empleados
es un NSArray
de una clase personalizada Person
).
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 expertos reales 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 de antemano!
Solución
De acuerdo con referencia de clase NSInvocation de Apple :
Un
NSInvocation
es un mensaje de Objective-C convertido en estático, es decir, es una acción convertida en un objeto.
Y, en un pequeño más 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. NSInvocation
es útil cuando desea enviar un mensaje a un objeto en un momento diferente, o enviar el mismo mensaje varias veces. NSInvocation
le 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ía el mensaje addObject:
de la siguiente manera:
[myArray addObject:myString];
Ahora, supongamos que desea utilizar NSInvocation
para enviar este mensaje en otro momento:
Primero, prepararía un objeto NSInvocation
para usar con el selector NSMutableArray
addObject:
selector:
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 Documentación de Apple para más detalles.
En este punto, myInvocation
es un objeto completo que describe un mensaje que se puede enviar. Para enviar el mensaje, debe llamar a:
[myInvocation invoke];
Este paso final hará que se envíe el mensaje, esencialmente ejecutando [myArray addObject: myString];
.
Piense en ello como enviar un correo electrónico. Abre un nuevo correo electrónico (objeto NSInvocation
), completa la dirección de la persona (objeto) a la que desea enviarlo, escribe un mensaje para el destinatario (especifique un selector
y argumentos), y luego haga clic en "enviar" (llame a invoke
).
Consulte Uso de NSInvocation para obtener más información. Ver Uso de NSInvocation si lo anterior no funciona.
NSUndoManager
usa objetos NSInvocation
para que pueda invertir los comandos. Esencialmente, lo que está haciendo es crear un objeto NSInvocation
para decir: "Hola, si desea deshacer lo que acabo de hacer, envíe este mensaje a ese objeto, con estos argumentos". Usted le da el objeto NSInvocation
al NSUndoManager
, y agrega ese objeto a una matriz de acciones imposibles de deshacer. Si el usuario invoca " 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.
Consulte Registro de operaciones de deshacer para obtener más detalles .
Otros consejos
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: @ " Hola " world: @ " world "];
: el método:
- Imprimir " Hola mundo! "
- Cree una NSMethodSignature para sí mismo.
- Cree y complete una NSInvocation, llamándose a sí misma.
- Pase la NSInvocation a un NSTimer
- El temporizador se activará (aproximadamente) 1 segundo, haciendo que se vuelva a llamar al método con sus argumentos originales.
- Repita.
Al final, obtendrá 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 self
debe seguir 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.
UPDATE:
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 método -retainArguments
. 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];
Podría intentar simplemente usar la biblioteca aquí, que es mucho mejor: http://cocoawithlove.com/2008/03/construct-nsinvocation-for-any-message.html
Construyo un ejemplo simple de llamar a varios tipos de métodos con NSInvocation.
Tuve problemas para llamar a varios parámetros usando obj_msgSend