NSInvocation for Dummies?

139

Como exatamente NSInvocationfunciona? Existe uma boa introdução?

Estou especificamente com problemas para entender como o código a seguir (da Cocoa Programming para Mac OS X, 3ª Edição ) funciona, mas também posso aplicar os conceitos independentemente do exemplo do tutorial. O código:

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

Eu entendo o que está tentando fazer. (BTW, employeesé NSArrayde uma Personclasse personalizada .)

Sendo um cara do .NET, tento associar conceitos desconhecidos de Obj-C e Cocoa a conceitos do .NET aproximadamente análogos. Isso é semelhante ao conceito de delegado do .NET, mas não é tipado?

Isso não está 100% claro no livro, então estou procurando algo suplementar de verdadeiros especialistas em cacau / Obj-C, novamente com o objetivo de entender o conceito fundamental sob o exemplo simples (-ish). Estou realmente procurando poder aplicar o conhecimento de forma independente - até o capítulo 9, eu não estava tendo dificuldade em fazer isso. Mas agora ...

Desde já, obrigado!

John Rudy
fonte

Respostas:

284

De acordo com a referência da classe NSInvocation da Apple :

An NSInvocationé uma mensagem Objective-C processada estática, ou seja, é uma ação transformada em objeto.

E, um pouco mais detalhadamente:

O conceito de mensagens é central na filosofia do objetivo-c. Sempre que você chama um método ou acessa uma variável de algum objeto, você está enviando uma mensagem. NSInvocationé útil quando você deseja enviar uma mensagem para um objeto em um momento diferente ou enviar a mesma mensagem várias vezes. NSInvocationpermite que você descreva a mensagem a ser enviada e, em seguida, invoque -a (na verdade, envie-a para o objeto de destino) posteriormente.


Por exemplo, digamos que você queira adicionar uma string a uma matriz. Você normalmente enviaria a addObject:mensagem da seguinte maneira:

[myArray addObject:myString];

Agora, digamos que você queira usar NSInvocationpara enviar esta mensagem em outro momento:

Primeiro, você prepararia um NSInvocationobjeto para uso com NSMutableArrayo addObject:seletor:

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

Em seguida, você especificaria para qual objeto enviar a mensagem:

[myInvocation setTarget:myArray];

Especifique a mensagem que você deseja enviar para esse objeto:

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

E preencha todos os argumentos para esse método:

[myInvocation setArgument:&myString atIndex:2];

Observe que os argumentos do objeto devem ser passados ​​pelo ponteiro. Agradecemos a Ryan McCuaig por apontar isso e consulte a documentação da Apple para obter mais detalhes.

Neste ponto, myInvocationé um objeto completo, descrevendo uma mensagem que pode ser enviada. Para realmente enviar a mensagem, você deve ligar para:

[myInvocation invoke];

Esta etapa final fará com que a mensagem seja enviada, essencialmente em execução [myArray addObject:myString];.

Pense nisso como enviar um email. Você abre um novo email ( NSInvocationobjeto), preenche o endereço da pessoa (objeto) para quem deseja enviá-lo, digita uma mensagem para o destinatário (especifique a selectore argumentos) e, em seguida, clique em "enviar" (chamada invoke)

Consulte Usando o NSInvocation para obter mais informações. Consulte Usando o NSInvocation se o acima não estiver funcionando.


NSUndoManagerusa NSInvocationobjetos para que possa reverter comandos. Essencialmente, o que você está fazendo é criar um NSInvocationobjeto para dizer: "Ei, se você quiser desfazer o que acabei de fazer, envie esta mensagem para esse objeto, com esses argumentos". Você dá o NSInvocationobjeto ao NSUndoManagere ele o adiciona a uma matriz de ações que podem ser desfeitas. Se o usuário chamar "Desfazer", NSUndoManagerbasta procurar a ação mais recente na matriz e chamar o arquivo armazenado.NSInvocation objeto para executar a ação necessária.

Consulte Registrando operações de desfazer para obter mais detalhes.

e.James
fonte
10
Uma pequena correção para uma excelente resposta ... você precisa passar um ponteiro para os objetos setArgument:atIndex:, para que a atribuição de arg seja realmente lida [myInvocation setArgument:&myString atIndex:2].
21499 Ryan McCuaig #
60
Apenas para esclarecer a observação de Ryan, o índice 0 é reservado para "próprio" e o índice 1 é reservado para "_cmd" (consulte o link e.James publicado para obter mais detalhes). Portanto, seu primeiro argumento é colocado no índice 2, o segundo argumento no índice 3, etc ...
Dave
4
@haroldcampbell: como devemos chamar?
precisa saber é o seguinte
6
Não entendo por que precisamos chamar setSelector, pois já especificamos o seletor em mySignature.
Gleno
6
@ Gleno: NSInvocation é bastante flexível. Você pode realmente definir qualquer seletor que corresponda à assinatura do método, para que você não precise necessariamente usar o mesmo seletor usado para criar a assinatura do método. Neste exemplo, você poderia facilmente configurar setSelector: @selector (removeObject :), pois eles compartilham a mesma assinatura de método.
e.James
48

Aqui está um exemplo simples de NSInvocation em ação:

- (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 chamado - [self hello:@"Hello" world:@"world"];- o método irá:

  • Imprimir "Olá, mundo!"
  • Crie um NSMethodSignature para si mesmo.
  • Crie e preencha um NSInvocation, chamando a si próprio.
  • Passe o NSInvocation para um NSTimer
  • O cronômetro será acionado em (aproximadamente) 1 segundo, fazendo com que o método seja chamado novamente com seus argumentos originais.
  • Repetir.

No final, você obterá uma impressão assim:

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

Obviamente, o objeto de destino selfdeve continuar existindo para o NSTimer enviar o NSInvocation para ele. Por exemplo, um objeto Singleton ou um AppDelegate que existe durante a duração do aplicativo.


ATUALIZAR:

Como observado acima, quando você passa um NSInvocation como argumento para um NSTimer, o NSTimer retém automaticamente todos os argumentos do NSInvocation.

Se você não está passando um NSInvocation como argumento para um NSTimer e planeja mantê-lo por um tempo, chame o -retainArgumentsmétodo Caso contrário, seus argumentos poderão ser desalocados antes que a chamada seja invocada, causando o travamento do seu código. Veja como fazê-lo:

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
fonte
6
É interessante que, embora o invocationWithMethodSignature:inicializador seja usado, você ainda precise ligar setSelector:. Parece redundante, mas acabei de testar e é necessário.
18118 ThomasW no
Isso continua sendo executado em um loop infinito? e o que é _cmd
j2emanue