140 votes

NSInvocation pour les nuls ?

Comment exactement NSInvocation travail ? Y a-t-il une bonne introduction ?

J'ai notamment du mal à comprendre comment le code suivant (tiré de Programmation Cocoa pour Mac OS X, 3ème édition ) fonctionne, mais aussi être capable d'appliquer les concepts indépendamment de l'échantillon du tutoriel. 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, employees est un NSArray d'une coutume Person classe.)

Étant un spécialiste de .NET, j'essaie d'associer les concepts Obj-C et Cocoa peu familiers à 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 cherche quelque chose de complémentaire de la part de vrais experts en Cocoa/Obj-C, toujours dans le but de comprendre le concept fondamental derrière l'exemple simple (ou presque). Je cherche vraiment à être capable d'appliquer les connaissances de manière indépendante. Jusqu'au chapitre 9, je n'avais aucune difficulté à le faire. Mais maintenant...

Merci d'avance !

288voto

e.James Points 51680

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

Un site NSInvocation est un message Objective-C rendu statique, c'est-à-dire qu'il s'agit d'une action transformée en objet.

Et, dans un petit plus de détails :

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


Par exemple, disons que vous voulez ajouter une chaîne à un tableau. Normalement, vous enverriez la commande addObject: comme suit :

[myArray addObject:myString];

Maintenant, disons que vous voulez utiliser NSInvocation pour envoyer ce message à un autre moment :

Tout d'abord, vous devez préparer un NSInvocation à utiliser avec NSMutableArray 's addObject: sélecteur :

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

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

[myInvocation setTarget:myArray];

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

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

Et remplissez les arguments éventuels pour cette méthode :

[myInvocation setArgument:&myString atIndex:2];

Notez que les arguments des objets doivent être passés par un pointeur. Merci à Ryan McCuaig pour l'avoir signalé, et veuillez voir La documentation d'Apple pour plus de détails.

A ce stade, myInvocation est un objet complet, décrivant un message qui peut être envoyé. Pour envoyer réellement le message, il faut appeler :

[myInvocation invoke];

Cette étape finale provoquera l'envoi du message, exécutant essentiellement [myArray addObject:myString]; .

Imaginez que vous envoyez un courrier électronique. Vous ouvrez un nouvel e-mail ( NSInvocation ), remplissez l'adresse de la personne (objet) à qui vous voulez l'envoyer, tapez un message pour le destinataire (spécifiez un nom d'utilisateur et un mot de passe). selector et des arguments), puis cliquez sur "envoyer" (appel invoke ).

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


NSUndoManager utilise NSInvocation afin qu'il puisse inverser commandes. Essentiellement, ce que vous faites c'est créer une NSInvocation pour dire : "Hé, si tu veux annuler ce que je viens de faire, envoie ce message à cet objet, avec ces arguments". Vous donnez à l'objet NSInvocation à l'objet NSUndoManager et il ajoute cet objet à un tableau d'actions annulables. Si l'utilisateur appelle "Undo", NSUndoManager recherche simplement l'action la plus récente dans le tableau, et invoque l'action stockée. NSInvocation pour effectuer l'action nécessaire.

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

10 votes

Une correction mineure à une réponse par ailleurs excellente... vous devez passer un pointeur aux objets dans setArgument:atIndex: donc l'affectation de l'arg devrait en fait se lire [myInvocation setArgument:&myString atIndex:2] .

0 votes

@Ryan McCuaig : Merci d'avoir signalé ce point. J'ai effectué la modification et ajouté un lien vers la documentation correspondante.

61 votes

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 posté par e.James pour plus de détails). Donc votre premier argument est placé à l'index 2, le second à l'index 3, etc...

49voto

Dave Gallagher Points 5314

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

Quand on vous appelle - [self hello:@"Hello" world:@"world"]; - la méthode le fera :

  • Imprimez "Hello world !"
  • Créer une NSMethodSignature pour lui-même.
  • Créer et remplir une NSInvocation, s'appelant elle-même.
  • Transmettre la NSInvocation à un NSTimer
  • La minuterie se déclenchera dans une seconde (environ), ce qui entraînera un nouvel appel de la méthode avec ses arguments d'origine.
  • Répète.

À la fin, vous obtiendrez une impression comme celle-ci :

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 sûr, l'objet cible self doit continuer à exister pour que le NSTimer puisse lui envoyer la NSInvocation. Par exemple, un Singleton ou un AppDelegate qui existe pour la durée de l'application.


UPDATE :

Comme indiqué plus haut, lorsque vous passez une NSInvocation comme argument à un NSTimer, ce dernier conserve automatiquement tous les arguments de la NSInvocation.

Si vous ne passez pas une NSInvocation en tant qu'argument à un NSTimer, et que vous prévoyez de le faire rester pendant un certain temps, vous devez appeler son -retainArguments méthode. Sinon, ses arguments risquent d'être désalloués avant l'invocation, provoquant éventuellement le plantage 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];

6 votes

Il est intéressant de noter que même si le invocationWithMethodSignature: est utilisé, vous devez toujours appeler setSelector: . Cela semble redondant, mais je viens de tester et c'est nécessaire.

0 votes

Est-ce que cela tourne en boucle infinie ? et qu'est-ce que _cmd ?

6voto

Casebash Points 22106

Vous pouvez essayer d'utiliser la bibliothèque ici qui est beaucoup plus agréable : http://cocoawithlove.com/2008/03/construct-nsinvocation-for-any-message.html

0voto

Aleph7 Points 1944

Voici une autre bibliothèque que vous pouvez utiliser : http://www.a-coding.com/2010/10/making-nsinvocations.html

0voto

brian.clear Points 2424

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

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

https://github.com/clearbrian/NSInvocation_Runtime

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X