NSInvocation pour les nuls?

139

Comment ça NSInvocationmarche exactement ? Y a-t-il une bonne introduction?

J'ai spécifiquement des problèmes pour comprendre le fonctionnement du code suivant (de Cocoa Programming pour Mac OS X, 3e édition ), mais je peux également appliquer les concepts indépendamment de l'exemple du didacticiel. Le 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];
}

Je comprends ce qu'il essaie de faire. (BTW, employeesest une classe NSArraypersonnalisée Person.)

Étant un gars .NET, j'essaie d'associer des concepts Obj-C et Cocoa inconnus à des concepts .NET à peu près analogues. Est-ce similaire au concept de délégué de .NET, mais non typé?

Ce n'est pas clair à 100% dans le livre, donc je recherche quelque chose de supplémentaire de la part de vrais experts Cocoa / Obj-C, toujours dans le but de comprendre le concept fondamental sous l'exemple simple (-ish). Je cherche vraiment à pouvoir appliquer les connaissances de manière indépendante - jusqu'au chapitre 9, je n'avais aucune difficulté à le faire. Mais maintenant ...

Merci d'avance!

John Rudy
la source

Réponses:

284

Selon la référence de classe NSInvocation d' Apple :

An NSInvocationest un message Objective-C rendu statique, c'est-à-dire une action transformée en objet.

Et, dans un peu plus en détail:

Le concept de messages est au cœur de la philosophie objective-c. Chaque fois que vous appelez une méthode ou accédez à une variable d'un objet, vous lui envoyez un message. NSInvocationest pratique lorsque vous souhaitez envoyer un message à un objet à un moment différent ou envoyer le même message plusieurs fois. NSInvocationvous permet de décrire le message que vous allez envoyer, puis de l' invoquer (en fait l'envoyer à l'objet cible) plus tard.


Par exemple, disons que vous souhaitez ajouter une chaîne à un tableau. Vous enverriez normalement le addObject:message comme suit:

[myArray addObject:myString];

Maintenant, disons que vous souhaitez utiliser NSInvocationpour envoyer ce message à un autre moment:

Tout d'abord, vous devez préparer un NSInvocationobjet à utiliser avec NSMutableArrayle addObject:sélecteur de:

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

Ensuite, vous spécifiez à quel objet envoyer le message:

[myInvocation setTarget:myArray];

Spécifiez le message que vous souhaitez envoyer à cet objet:

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

Et remplissez tous les arguments pour cette méthode:

[myInvocation setArgument:&myString atIndex:2];

Notez que les arguments d'objet doivent être passés par pointeur. Merci à Ryan McCuaig de l' avoir signalé et veuillez consulter la documentation d' Apple pour plus de détails.

À ce stade, myInvocationest un objet complet, décrivant un message qui peut être envoyé. Pour envoyer réellement le message, vous appelleriez:

[myInvocation invoke];

Cette dernière étape provoquera l'envoi du message, essentiellement en cours d'exécution [myArray addObject:myString];.

Pensez-y comme envoyer un e-mail. Vous ouvrez un nouvel e-mail ( NSInvocationobjet), renseignez l’adresse de la personne (objet) à laquelle vous voulez l’envoyer, saisissez un message pour le destinataire (indiquez un selectoret des arguments), puis cliquez sur «envoyer» (appelez invoke).

Consultez Utilisation de NSInvocation pour plus d'informations. Consultez Utilisation de NSInvocation si ce qui précède ne fonctionne pas.


NSUndoManagerutilise des NSInvocationobjets pour pouvoir inverser les commandes. Essentiellement, ce que vous faites est de créer un NSInvocationobjet pour dire: "Hé, si vous voulez annuler ce que je viens de faire, envoyez ce message à cet objet, avec ces arguments". Vous donnez l' NSInvocationobjet au NSUndoManager, et il ajoute cet objet à un tableau d'actions annulables. Si l'utilisateur appelle «Annuler», NSUndoManageril recherche simplement l'action la plus récente dans le tableau et appelle l' NSInvocationobjet stocké pour effectuer l'action nécessaire.

Voir Enregistrement des opérations d'annulation pour plus de détails.

e.James
la source
10
Une correction mineure à une réponse par ailleurs excellente ... vous devez passer un pointeur vers les objets dans setArgument:atIndex:, donc l'affectation arg devrait réellement lire [myInvocation setArgument:&myString atIndex:2].
Ryan McCuaig
60
Juste pour clarifier la note de Ryan, l'index 0 est réservé pour "self" et l'index 1 est réservé pour "_cmd" (voir le lien e.James posté pour plus de détails). Donc, votre premier argument est placé à l'index 2, le deuxième argument à l'index 3, etc ...
Dave
4
@haroldcampbell: comment faut-il appeler?
e.James
6
Je ne comprends pas pourquoi nous devons appeler setSelector, puisque nous avons déjà spécifié le sélecteur dans mySignature.
Gleno
6
@Gleno: NSInvocation est assez flexible. Vous pouvez en fait définir n'importe quel sélecteur qui correspond à la signature de méthode, vous n'avez donc pas nécessairement besoin d'utiliser le même sélecteur que celui utilisé pour créer la signature de méthode. Dans cet exemple, vous pouvez tout aussi facilement faire setSelector: @selector (removeObject :), car ils partagent la même signature de méthode.
e.James
48

Voici un exemple simple de NSInvocation en action:

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

Lorsqu'elle est appelée - [self hello:@"Hello" world:@"world"];- la méthode:

  • Imprimer "Bonjour tout le monde!"
  • Créez un NSMethodSignature pour lui-même.
  • Créez et remplissez une NSInvocation en s'appelant elle-même.
  • Transmettez NSInvocation à un NSTimer
  • Le temporisateur se déclenchera dans (environ) 1 seconde, provoquant le nouvel appel de la méthode avec ses arguments d'origine.
  • Répéter.

À la fin, vous obtiendrez une impression comme ceci:

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

Bien entendu, l'objet cible selfdoit continuer d'exister pour que le NSTimer lui envoie le NSInvocation. Par exemple, un objet Singleton ou un AppDelegate qui existe pendant la durée de l'application.


METTRE À JOUR:

Comme indiqué ci-dessus, lorsque vous passez un NSInvocation en tant qu'argument à un NSTimer, le NSTimer conserve automatiquement tous les arguments de NSInvocation.

Si vous ne passez pas une NSInvocation en tant qu'argument à un NSTimer et que vous prévoyez de la laisser rester un moment, vous devez appeler sa -retainArgumentsméthode. Sinon, ses arguments peuvent être désalloués avant que l'invocation ne soit invoquée, provoquant éventuellement le blocage de votre code. Voici comment procéder:

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
la source
6
Il est intéressant de noter que même si l' invocationWithMethodSignature:initialiseur est utilisé, vous devez toujours appeler setSelector:. Cela semble redondant, mais je viens de tester et c'est nécessaire.
ThomasW
Est-ce que cela continue de fonctionner dans une boucle infinie? et qu'est-ce que _cmd
j2emanue
0

Je construis un exemple simple d'appel de divers types de méthodes avec NSInvocation.

J'ai eu des problèmes pour appeler plusieurs paramètres en utilisant obj_msgSend

https://github.com/clearbrian/NSInvocation_Runtime

brian.clear
la source