NSInvocation for Dummies?


139

Come funziona esattamente NSInvocation? C'è una buona introduzione?

In particolare ho problemi a capire come funziona il seguente codice (da Cocoa Programming per Mac OS X, 3a edizione ), ma poi posso anche applicare i concetti indipendentemente dall'esempio del tutorial. Il codice:

- (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];
}

Ho capito cosa sta cercando di fare. (A proposito, employeesè NSArraydi una Personclasse personalizzata .)

Essendo un ragazzo di .NET, cerco di associare concetti Obj-C e Cocoa non familiari a concetti di .NET approssimativamente analoghi. È simile al concetto delegato di .NET, ma non tipizzato?

Questo non è chiaro al 100% dal libro, quindi sto cercando qualcosa di supplementare da veri esperti di Cocoa / Obj-C, sempre con l'obiettivo di comprendere il concetto fondamentale sotto il semplice esempio (-ish). Sto davvero cercando di essere in grado di applicare autonomamente le conoscenze - fino al capitolo 9, non avevo difficoltà a farlo. Ma ora ...

Grazie in anticipo!

Risposte:


284

Secondo il riferimento di classe NSInvocation di Apple :

An NSInvocationè un messaggio Objective-C reso statico, ovvero è un'azione trasformata in un oggetto.

E, un po ' più in dettaglio:

Il concetto di messaggi è centrale nella filosofia dell'ob-c. Ogni volta che chiami un metodo o accedi a una variabile di un oggetto, lo stai inviando un messaggio. NSInvocationè utile quando si desidera inviare un messaggio a un oggetto in un momento diverso oppure inviare lo stesso messaggio più volte. NSInvocationconsente di descrivere il messaggio che si intende inviare e quindi invocarlo (effettivamente inviarlo all'oggetto di destinazione) in un secondo momento.


Ad esempio, supponiamo che si desideri aggiungere una stringa a un array. Normalmente invieresti il addObject:messaggio come segue:

[myArray addObject:myString];

Ora, supponiamo che tu voglia utilizzare NSInvocationper inviare questo messaggio in qualche altro momento:

Innanzitutto, prepareresti un NSInvocationoggetto da usare con NSMutableArrayil addObject:selettore di:

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

Successivamente, dovresti specificare a quale oggetto inviare il messaggio:

[myInvocation setTarget:myArray];

Specifica il messaggio che desideri inviare a quell'oggetto:

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

E compila tutti gli argomenti per quel metodo:

[myInvocation setArgument:&myString atIndex:2];

Si noti che gli argomenti dell'oggetto devono essere passati dal puntatore. Grazie a Ryan McCuaig per averlo sottolineato, e per favore vedi la documentazione di Apple per maggiori dettagli.

A questo punto, myInvocationè un oggetto completo, che descrive un messaggio che può essere inviato. Per inviare effettivamente il messaggio, è necessario chiamare:

[myInvocation invoke];

Questo passaggio finale farà sì che il messaggio venga inviato, essenzialmente in esecuzione [myArray addObject:myString];.

Pensa a come inviare una e-mail. Si apre una nuova e-mail ( NSInvocationoggetto), si inserisce l'indirizzo della persona (oggetto) a cui si desidera inviarlo, si digita un messaggio per il destinatario (specificare unselector e argomenti), quindi si fa clic su "invia" (chiama invoke).

Vedere Utilizzo di NSInvocation per ulteriori informazioni. Vedere Utilizzo di NSInvocation se quanto sopra non funziona.


NSUndoManagerusa gli NSInvocationoggetti in modo che possa invertire i comandi. In sostanza, quello che stai facendo è creare un NSInvocationoggetto per dire: "Ehi, se vuoi annullare ciò che ho appena fatto, invia questo messaggio a quell'oggetto, con questi argomenti". Dai l' NSInvocationoggetto a NSUndoManager, e aggiunge l'oggetto a una serie di azioni annullabili. Se l'utente chiama "Annulla",NSUndoManager cerca semplicemente l'azione più recente dell'array e invoca l' NSInvocationoggetto memorizzato per eseguire l'azione necessaria.

Vedere Registrazione delle operazioni di annullamento per ulteriori dettagli.


10
Una correzione minore a una risposta altrimenti eccellente ... devi passare un puntatore agli oggetti setArgument:atIndex:, quindi l'assegnazione arg dovrebbe effettivamente leggere [myInvocation setArgument:&myString atIndex:2].
Ryan McCuaig,

60
Solo per chiarire la nota di Ryan, l'indice 0 è riservato per "self" e l'indice 1 è riservato per "_cmd" (vedere il link e.James pubblicato per maggiori dettagli). Quindi il tuo primo argomento viene inserito nell'indice 2, il secondo argomento nell'indice 3, ecc ...
Dave,

4
@haroldcampbell: come dobbiamo chiamare?
e.James

6
Non capisco perché dobbiamo chiamare setSelector, poiché abbiamo già specificato il selettore in mySignature.
Gleno,

6
@Gleno: NSInvocation è abbastanza flessibile. Puoi effettivamente impostare qualsiasi selettore che corrisponda alla firma del metodo, quindi non devi necessariamente utilizzare lo stesso selettore utilizzato per creare la firma del metodo. In questo esempio, puoi impostare altrettanto facilmente setSelector: @selector (removeObject :), poiché condividono la stessa firma del metodo.
e.James

48

Ecco un semplice esempio di NSInvocation in azione:

- (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];
}

Quando chiamato - [self hello:@"Hello" world:@"world"];- il metodo sarà:

  • Stampa "Ciao mondo!"
  • Crea un NSMethodSignature per se stesso.
  • Crea e popola un NSInvocation, chiamando se stesso.
  • Passare NSInvocation a un NSTimer
  • Il timer si attiverà in (circa) 1 secondo, provocando la richiamata del metodo con i suoi argomenti originali.
  • Ripetere.

Alla fine, otterrai una stampa in questo 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!
...

Ovviamente, l'oggetto target selfdeve continuare a esistere affinché NSTimer gli invii NSInvocation. Ad esempio, un oggetto Singleton o un AppDelegate che esiste per la durata dell'applicazione.


AGGIORNARE:

Come notato sopra, quando si passa un NSInvocation come argomento a un NSTimer, NSTimer conserva automaticamente tutti gli argomenti di NSInvocation.

Se non si trasmette un NSInvocation come argomento a un NSTimer e si prevede di mantenerlo attivo per un po ', è necessario chiamarne il -retainArgumentsmetodo. Altrimenti i suoi argomenti potrebbero essere deallocati prima che venga invocata l'invocazione, causando un arresto anomalo del codice. Ecco come farlo:

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
È interessante notare che anche se l' invocationWithMethodSignature:inizializzatore viene utilizzato, è comunque necessario chiamare setSelector:. Sembra ridondante, ma ho appena testato ed è necessario.
ThomasW,

Questo continua a funzionare in un ciclo infinito? e cosa è _cmd
j2emanue il


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.