NSInvocation für Dummies?


139

Wie genau funktioniert das NSInvocation? Gibt es eine gute Einführung?

Ich habe speziell Probleme zu verstehen, wie der folgende Code (aus Cocoa Programming für Mac OS X, 3. Ausgabe ) funktioniert, kann dann aber auch die Konzepte unabhängig vom Tutorial-Beispiel anwenden. Der Code:

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

Ich verstehe, was es versucht zu tun. (Übrigens employeesgehört NSArrayzu einer benutzerdefinierten PersonKlasse.)

Als .NET-Typ versuche ich, unbekannte Obj-C- und Cocoa-Konzepte mit ungefähr analogen .NET-Konzepten zu verknüpfen. Ist dies dem Delegatenkonzept von .NET ähnlich, aber untypisiert?

Dies geht aus dem Buch nicht zu 100% hervor, daher suche ich nach einer Ergänzung von echten Cocoa / Obj-C-Experten, wieder mit dem Ziel, das grundlegende Konzept unter dem einfachen (-ish) Beispiel zu verstehen. Ich bin wirklich bestrebt, das Wissen unabhängig anwenden zu können - bis Kapitel 9 hatte ich keine Schwierigkeiten damit. Aber jetzt ...

Danke im Voraus!

Antworten:


284

Laut Apples NSInvocation-Klassenreferenz :

An NSInvocationist eine Objective-C-Nachricht, die statisch gerendert wird, dh eine Aktion, die in ein Objekt umgewandelt wurde.

Und etwas genauer:

Das Konzept der Botschaften spielt eine zentrale Rolle in der objektiven Philosophie. Jedes Mal, wenn Sie eine Methode aufrufen oder auf eine Variable eines Objekts zugreifen, senden Sie ihr eine Nachricht. NSInvocationDies ist praktisch, wenn Sie eine Nachricht zu einem anderen Zeitpunkt an ein Objekt senden oder dieselbe Nachricht mehrmals senden möchten. NSInvocationMit dieser Option können Sie die Nachricht beschreiben, die Sie senden möchten, und sie später aufrufen (tatsächlich an das Zielobjekt senden).


Angenommen, Sie möchten einem Array eine Zeichenfolge hinzufügen. Normalerweise senden Sie die addObject:Nachricht wie folgt:

[myArray addObject:myString];

Angenommen, Sie möchten NSInvocationdiese Nachricht zu einem anderen Zeitpunkt senden:

Zunächst bereiten Sie ein NSInvocationObjekt für die Verwendung mit NSMutableArraydem addObject:Selektor vor:

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

Als Nächstes geben Sie an, an welches Objekt die Nachricht gesendet werden soll:

[myInvocation setTarget:myArray];

Geben Sie die Nachricht an, die Sie an dieses Objekt senden möchten:

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

Und geben Sie alle Argumente für diese Methode ein:

[myInvocation setArgument:&myString atIndex:2];

Beachten Sie, dass Objektargumente per Zeiger übergeben werden müssen. Vielen Dank an Ryan McCuaig für diesen Hinweis. Weitere Informationen finden Sie in der Apple-Dokumentation .

Zu diesem Zeitpunkt myInvocationhandelt es sich um ein vollständiges Objekt, das eine Nachricht beschreibt, die gesendet werden kann. Um die Nachricht tatsächlich zu senden, würden Sie anrufen:

[myInvocation invoke];

Dieser letzte Schritt bewirkt, dass die Nachricht gesendet wird und im Wesentlichen ausgeführt wird [myArray addObject:myString];.

Stellen Sie sich vor, Sie senden eine E-Mail. Sie öffnen eine neue E-Mail ( NSInvocationObjekt), geben die Adresse der Person (Objekt) ein, an die Sie sie senden möchten, geben eine Nachricht für den Empfänger ein (geben Sie ein selectorund Argumente an) und klicken dann auf "Senden" (Anruf) invoke).

Weitere Informationen finden Sie unter Verwenden von NSInvocation . Siehe Verwenden von NSInvocation, wenn das oben genannte nicht funktioniert.


NSUndoManagerverwendet NSInvocationObjekte, um Befehle umzukehren . Im Wesentlichen erstellen Sie ein NSInvocationObjekt, um zu sagen: "Hey, wenn Sie rückgängig machen möchten, was ich gerade getan habe, senden Sie diese Nachricht mit diesen Argumenten an dieses Objekt." Sie geben das NSInvocationObjekt an NSUndoManagerund es fügt dieses Objekt einem Array von rückgängig zu machenden Aktionen hinzu. Wenn der Benutzer "Rückgängig" aufruft, NSUndoManagersucht er einfach nach der neuesten Aktion im Array und ruft das gespeicherte NSInvocationObjekt auf, um die erforderliche Aktion auszuführen.

Weitere Informationen finden Sie unter Registrieren von Rückgängig-Vorgängen .


10
Eine kleine Korrektur für eine ansonsten hervorragende Antwort ... Sie müssen einen Zeiger auf Objekte in übergeben setArgument:atIndex:, damit die Argumentzuweisung tatsächlich gelesen werden sollte [myInvocation setArgument:&myString atIndex:2].
Ryan McCuaig

60
Um Ryans Anmerkung zu verdeutlichen, ist Index 0 für "self" und Index 1 für "_cmd" reserviert (siehe den Link e.James für weitere Details). Ihr erstes Argument wird also bei Index 2 platziert, das zweite bei Index 3 usw.
Dave

4
@ Haroldcampbell: Wie müssen wir anrufen?
e.James

6
Ich verstehe nicht, warum wir setSelector aufrufen müssen, da wir den Selektor bereits in mySignature angegeben haben.
Gleno

6
@Gleno: NSInvocation ist sehr flexibel. Sie können tatsächlich jeden Selektor festlegen, der der Methodensignatur entspricht, sodass Sie nicht unbedingt denselben Selektor verwenden müssen, der zum Erstellen der Methodensignatur verwendet wurde. In diesem Beispiel können Sie setSelector: @selector (removeObject :) genauso einfach ausführen, da sie dieselbe Methodensignatur verwenden.
e.James

48

Hier ist ein einfaches Beispiel für NSInvocation in Aktion:

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

Beim Aufruf von - [self hello:@"Hello" world:@"world"];- wird die Methode:

  • Drucken Sie "Hallo Welt!"
  • Erstellen Sie eine NSMethodSignature für sich.
  • Erstellen und füllen Sie eine NSInvocation, die sich selbst aufruft.
  • Übergeben Sie die NSInvocation an einen NSTimer
  • Der Timer wird in (ungefähr) 1 Sekunde ausgelöst, wodurch die Methode mit ihren ursprünglichen Argumenten erneut aufgerufen wird.
  • Wiederholen.

Am Ende erhalten Sie einen Ausdruck wie folgt:

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!
...

Natürlich muss das Zielobjekt selfweiterhin vorhanden sein, damit der NSTimer die NSInvocation an ihn senden kann. Zum Beispiel ein Singleton- Objekt oder ein AppDelegate, das für die Dauer der Anwendung vorhanden ist.


AKTUALISIEREN:

Wie oben erwähnt, behält der NSTimer automatisch alle Argumente der NSInvocation bei, wenn Sie eine NSInvocation als Argument an einen NSTimer übergeben.

Wenn Sie eine NSInvocation nicht als Argument an einen NSTimer übergeben und planen, dass sie eine Weile bleibt, müssen Sie ihre -retainArgumentsMethode aufrufen . Andernfalls werden die Argumente möglicherweise freigegeben, bevor der Aufruf aufgerufen wird, was schließlich zum Absturz Ihres Codes führt. So geht's:

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 ist interessant, dass Sie trotz Verwendung des invocationWithMethodSignature:Initialisierers immer noch aufrufen müssen setSelector:. Es scheint überflüssig, aber ich habe es gerade getestet und es ist notwendig.
ThomasW

Läuft das in einer Endlosschleife weiter? und was ist _cmd
j2emanue


Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.