NSInvocation for Dummies?

时间:2008-11-24 04:17:49

标签: objective-c cocoa undo-redo

NSInvocation究竟是如何运作的?有一个很好的介绍吗?

我特别遇到了解以下代码(来自 Mac OS X的Cocoa编程,第3版)如何工作的问题,但是也能够独立于教程示例应用这些概念。代码:

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

我得到了它想做的事情。 (顺便说一句,employees是自定义NSArray课程的Person。)

作为一个.NET人,我尝试将不熟悉的Obj-C和Cocoa概念与大致相似的.NET概念联系起来。这类似于.NET的委托概念,但是无类型?

这本书并非100%明确,所以我正在寻找真正的Cocoa / Obj-C专家的补充,同样我的目标是理解简单(-ish)示例下的基本概念。我真的希望能够独立应用这些知识 - 直到第9章,我没有遇到任何困难。但现在......

提前致谢!

4 个答案:

答案 0 :(得分:278)

根据Apple's NSInvocation class reference

  

NSInvocation是一个静态渲染的Objective-C消息,也就是说,它是一个变成对象的动作。

并且,在 little 更详细信息中:

信息的概念是客观c哲学的核心。无论何时调用方法或访问某个对象的变量,都会向其发送消息。当您想要在不同的时间点向对象发送消息或多次发送相同的消息时,NSInvocation会派上用场。 NSInvocation允许您描述您要发送的消息,然后调用(实际上将其发送到目标对象)。


例如,假设您要将字符串添加到数组中。您通常会发送addObject:消息,如下所示:

[myArray addObject:myString];

现在,假设你想使用NSInvocation在其他时间点发送此消息:

首先,您要准备一个NSInvocation对象,以便与NSMutableArray的{​​{1}}选择器一起使用:

addObject:

接下来,您将指定将消息发送到的对象:

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

指定要发送给该对象的消息:

[myInvocation setTarget:myArray];

并填写该方法的任何参数:

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

请注意,对象参数必须通过指针传递。感谢您Ryan McCuaig指出这一点,有关详细信息,请参阅Apple's documentation

此时,[myInvocation setArgument:&myString atIndex:2]; 是一个完整的对象,描述了可以发送的消息。要实际发送消息,您可以致电:

myInvocation

此最后一步将导致发送消息,基本上执行[myInvocation invoke];

将其视为发送电子邮件。您打开一个新电子邮件([myArray addObject:myString];对象),填写要将其发送给的人(对象)的地址,键入收件人的消息(指定NSInvocation和参数),然后点击“发送”(致电selector)。

有关详细信息,请参阅Using NSInvocation。 如果上述内容无效,请参阅Using NSInvocation


invoke使用NSUndoManager个对象,以便反向命令。从本质上讲,你正在做的是创建一个NSInvocation对象来说:“嘿,如果你想撤消我刚刚做的事情,请将这条消息发送给该对象,并使用这些参数”。您将NSInvocation对象提供给NSInvocation,并将该对象添加到可撤消操作数组中。如果用户调用“撤消”,NSUndoManager只会查找数组中最近的操作,并调用存储的NSUndoManager对象来执行必要的操作。

有关详细信息,请参阅Registering Undo Operations

答案 1 :(得分:46)

以下是NSInvocation的一个简单示例:

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

当被叫 - [self hello:@"Hello" world:@"world"];时 - 方法将:

  • 打印“Hello world!”
  • 为自己创建一个NSMethodSignature。
  • 创建并填充NSInvocation,自行调用。
  • 将NSInvocation传递给NSTimer
  • 计时器将在(大约)1秒内触发,导致使用其原始参数再次调用该方法。
  • 重复。

最后,你会得到一个类似的打印输出:

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

当然,目标对象self必须继续存在,NSTimer才能将NSInvocation发送给它。例如, Singleton 对象,或者在应用程序持续时间内存在的AppDelegate。


<强>更新

如上所述,当您将NSInvocation作为参数传递给NSTimer时,NSTimer会自动保留所有NSInvocation的参数。

如果您没有将NSInvocation作为参数传递给NSTimer,并计划让它保持一段时间,则必须调用其-retainArguments方法。否则,在调用调用之前可能会释放其参数,最终导致代码崩溃。这是如何做到的:

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

答案 2 :(得分:5)

答案 3 :(得分:0)

我构建了一个使用NSInvocation调用各种方法类型的简单示例。

我在使用obj_msgSend

调用多个参数时遇到问题

https://github.com/clearbrian/NSInvocation_Runtime