NSInvocation for Dummies?

139

Jak dokładnie NSInvocationdziała? Czy jest dobre wprowadzenie?

Mam konkretnie problemy ze zrozumieniem, jak działa następujący kod (z Cocoa Programming for Mac OS X, 3rd Edition ), ale mogę również zastosować koncepcje niezależnie od przykładu samouczka. Kod:

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

Rozumiem, co próbuje zrobić. (BTW, employeesjest klasą NSArrayniestandardową Person.)

Będąc gościem .NET, staram się kojarzyć nieznane mi pojęcia Obj-C i Cocoa z z grubsza analogicznymi koncepcjami .NET. Czy jest to podobne do koncepcji delegata .NET, ale bez typu?

Nie jest to w 100% jasne z książki, dlatego szukam czegoś uzupełniającego od prawdziwych ekspertów od Cocoa / Obj-C, ponownie mając na celu zrozumienie podstawowej koncepcji pod prostym (-kawym) przykładem. Naprawdę chcę móc samodzielnie zastosować wiedzę - do rozdziału 9 nie miałem z tym żadnych trudności. Ale teraz ...

Z góry dziękuję!

John Rudy
źródło

Odpowiedzi:

284

Zgodnie z odniesieniem do klasy NSInvocation firmy Apple :

Jest NSInvocationto wiadomość Objective-C renderowana jako statyczna, to znaczy jest to akcja zamieniona w obiekt.

I trochę bardziej szczegółowo:

Pojęcie komunikatów ma kluczowe znaczenie dla filozofii obiektywnej c. Za każdym razem, gdy wywołujesz metodę lub uzyskujesz dostęp do zmiennej jakiegoś obiektu, wysyłasz do niego wiadomość. NSInvocationprzydaje się, gdy chcesz wysłać wiadomość do obiektu w innym momencie lub wysłać tę samą wiadomość kilka razy. NSInvocationpozwala opisać wiadomość, którą zamierzasz wysłać, a następnie wywołać ją (a właściwie wysłać do obiektu docelowego) później.


Na przykład, powiedzmy, że chcesz dodać ciąg do tablicy. Zwykle wysyłasz addObject:wiadomość w następujący sposób:

[myArray addObject:myString];

Teraz załóżmy, że chcesz użyć NSInvocationtej wiadomości do wysłania w innym momencie:

Po pierwsze, należy przygotować NSInvocationobiekt do użytku z NSMutableArray„s addObject:selektora:

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

Następnie należy określić, do którego obiektu ma zostać wysłana wiadomość:

[myInvocation setTarget:myArray];

Określ wiadomość, którą chcesz wysłać do tego obiektu:

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

I podaj wszystkie argumenty dla tej metody:

[myInvocation setArgument:&myString atIndex:2];

Zauważ, że argumenty obiektu muszą być przekazywane przez wskaźnik. Dziękuję Ryanowi McCuaigowi za zwrócenie na to uwagi i zapoznaj się z dokumentacją Apple, aby uzyskać więcej informacji.

W tym miejscu myInvocationznajduje się kompletny obiekt, opisujący wiadomość, którą można wysłać. Aby faktycznie wysłać wiadomość, zadzwoniłbyś:

[myInvocation invoke];

Ten ostatni krok spowoduje wysłanie wiadomości, zasadniczo wykonanie [myArray addObject:myString];.

Pomyśl o tym jak o wysyłaniu e-maila. Otwierasz nowy e-mail ( NSInvocationobiekt), wpisujesz adres osoby (obiektu), do którego chcesz go wysłać, wpisujesz wiadomość dla odbiorcy (określ a selectori argumenty), a następnie klikasz „wyślij” (zadzwońinvoke ).

Aby uzyskać więcej informacji, zobacz Używanie NSInvocation . Zobacz Używanie NSInvocation, jeśli powyższe nie działa.


NSUndoManagerużywa NSInvocationobiektów, aby móc odwrócić polecenia. Zasadniczo to, co robisz, to tworzenie NSInvocationobiektu, który mówi: „Hej, jeśli chcesz cofnąć to, co właśnie zrobiłem, wyślij tę wiadomość do tego obiektu, z tymi argumentami”. Dasz NSInvocationsprzeciwić się NSUndoManageri dodaje, że obiekt do tablicy działań cofnąć. Jeśli użytkownik wywoła „Cofnij”, NSUndoManagerpo prostu wyszukuje ostatnią akcję w tablicy i wywołuje przechowywany NSInvocationobiekt, aby wykonać niezbędne działanie.

Aby uzyskać więcej informacji, zobacz Rejestrowanie operacji cofania .

e.James
źródło
10
Jedna drobna poprawka do znakomitej odpowiedzi ... musisz przekazać wskaźnik do obiektów w setArgument:atIndex:, więc przypisanie argumentu powinno faktycznie czytać [myInvocation setArgument:&myString atIndex:2].
Ryan McCuaig
60
Żeby wyjaśnić uwagę Ryana, indeks 0 jest zarezerwowany dla „siebie”, a indeks 1 jest zarezerwowany dla „_cmd” (więcej szczegółów można znaleźć pod linkiem zamieszczonym przez e.James). Więc twój pierwszy argument zostanie umieszczony pod indeksem 2, drugi argument pod indeksem 3, itd ...
Dave,
4
@haroldcampbell: co mamy zadzwonić?
James,
6
Nie rozumiem, dlaczego musimy wywołać setSelector, skoro już określiliśmy selektor w mySignature.
Gleno
6
@Gleno: NSInvocation jest dość elastyczny. W rzeczywistości możesz ustawić dowolny selektor, który pasuje do sygnatury metody, więc niekoniecznie musisz używać tego samego selektora, który został użyty do utworzenia sygnatury metody. W tym przykładzie możesz równie łatwo zrobić setSelector: @selector (removeObject :), ponieważ mają one tę samą sygnaturę metody.
e.James
48

Oto prosty przykład NSInvocation w akcji:

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

Po wywołaniu - [self hello:@"Hello" world:@"world"];- metoda:

  • Drukuj "Witaj, świecie!"
  • Utwórz dla siebie NSMethodSignature.
  • Utwórz i wypełnij NSInvocation, wywołując samą siebie.
  • Przekaż NSInvocation do NSTimer
  • Licznik czasu zostanie uruchomiony w (w przybliżeniu) 1 sekundzie, powodując ponowne wywołanie metody z oryginalnymi argumentami.
  • Powtarzać.

Na koniec otrzymasz taki wydruk:

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

Oczywiście obiekt docelowy selfmusi nadal istnieć, aby NSTimer mógł wysłać do niego NSInvocation. Na przykład obiekt Singleton lub AppDelegate, który istnieje na czas trwania aplikacji.


AKTUALIZACJA:

Jak wspomniano powyżej, kiedy przekazujesz NSInvocation jako argument do NSTimer, NSTimer automatycznie zachowuje wszystkie argumenty NSInvocation.

Jeśli nie przekazujesz NSInvocation jako argumentu do NSTimer i planujesz trzymać go przez jakiś czas, musisz wywołać jego -retainArgumentsmetodę. W przeciwnym razie jego argumenty mogą zostać cofnięte przed wywołaniem wywołania, co ostatecznie spowoduje awarię kodu. Oto jak to zrobić:

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
źródło
6
Ciekawe, że nawet jeśli invocationWithMethodSignature:używany jest inicjator, nadal musisz zadzwonić setSelector:. Wydaje się zbędne, ale właśnie przetestowałem i jest to konieczne.
ThomasW
Czy to działa w nieskończonej pętli? a co to jest _cmd
j2emanue