objective-c cocoa undo-redo

objective c - NSInvocación para Dummies?



objective-c cocoa (4)

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

Estoy teniendo problemas específicamente para comprender cómo funciona el siguiente código (de la Programación Cocoa para Mac OS X, 3ª Edición ), pero también puedo aplicar los conceptos independientemente de la muestra 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, los employees son un NSArray de una clase de Person personalizada).

Siendo un tipo .NET, intento asociar conceptos desconocidos de Obj-C y Cocoa a conceptos de .NET aproximadamente 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 que entienda 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 por adelantado!


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!"
  • Crea un NSMethodSignature por sí mismo.
  • Crea y completa una invocación de NS, llamándose a sí mismo.
  • Pase la Invocación NS a un NSTimer
  • El temporizador se activará (aproximadamente) 1 segundo, haciendo que el método sea llamado de nuevo con sus argumentos originales.
  • Repetir.

Al final, obtendrás una impresión 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 autoobjeto objetivo debe continuar existiendo para que el NSTimer le envíe la Invocación NS. Por ejemplo, un objeto Singleton o un AppDelegate que existe durante la aplicación.

ACTUALIZAR:

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

Si no está pasando una Invocación NS como argumento a un NSTimer, y planea mantenerlo 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];



De acuerdo con la referencia de clase NSInvocation de Apple :

Una NSInvocation es un mensaje de Objective-C renderizado estático, es decir, es una acción convertida en un objeto.

Y, en un poco más de detalle:

El concepto de mensajes es central para la filosofía objetivo-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 punto diferente en el tiempo, o enviar el mismo mensaje varias veces. NSInvocation 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: siguiente manera:

[myArray addObject:myString];

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

En primer lugar, debe preparar un objeto de NSInvocation para su uso con addObject: selector de addObject: :

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

A continuación, debe especificar 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 del objeto deben pasarse por puntero. Gracias a Ryan McCuaig por señalarlo, y ver la 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 realmente el mensaje, llamarías:

[myInvocation invoke];

Este paso final hará que se envíe el mensaje, esencialmente ejecutando [myArray addObject:myString]; .

Piense que es como enviar un correo electrónico. Abre un nuevo correo electrónico (objeto NSInvocation ), 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" ( invoke llamada).

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

NSUndoManager usa objetos NSInvocation para que pueda revertir comandos. Esencialmente, lo que estás haciendo es crear un objeto de NSInvocation para decir: "Oye, si quieres deshacer lo que acabo de hacer, envía este mensaje a ese objeto, con estos argumentos". Le da el objeto NSInvocation al NSUndoManager , y agrega ese objeto a una matriz de acciones que se pueden deshacer. 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.

Consulte Registro de Operaciones de Deshacer para más detalles.