NSInvocation为假人?


139

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

我知道它正在尝试做什么。(顺便说一句,employeesNSArray自定义Person类的。)

作为.NET的人,我尝试将不熟悉的Obj-C和Cocoa概念与大致类似的.NET概念相关联。这是否类似于.NET的委托概念,但未键入?

从书中这还不是100%清楚的,所以我正在寻找真正的Cocoa / Obj-C专家的补充,其目的是让我理解简单(-ish)示例下的基本概念。我真的希望能够独立地应用这些知识-直到第9章,我都没有遇到任何困难。但现在 ...

提前致谢!

Answers:


284

根据Apple的NSInvocation类参考

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

而且,更详细一点

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


例如,假设您要向数组添加字符串。通常,您将addObject:按照以下方式发送消息:

[myArray addObject:myString];

现在,假设您要用于NSInvocation在其他时间发送此消息:

首先,您需要准备一个NSInvocationNSMutableArrayaddObject:选择器一起使用的对象:

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

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

[myInvocation setTarget:myArray];

指定您希望发送给该对象的消息:

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

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

[myInvocation setArgument:&myString atIndex:2];

请注意,对象参数必须由指针传递。感谢Ryan McCuaig指出这一点,请参阅Apple的文档以了解更多详细信息。

此时,myInvocation是一个完整的对象,描述了可以发送的消息。要实际发送消息,请致电:

[myInvocation invoke];

最后一步将导致消息被发送,实际上是在执行[myArray addObject:myString];

将其视为发送电子邮件。您打开一个新的电子邮件(NSInvocation对象),填写您要发送给的人(对象)的地址,为收件人键入一条消息(指定selector和参数),然后单击“发送”(呼叫invoke)。

有关更多信息,请参见使用NSInvocation。如果上述方法无效,请参阅使用NSInvocation


NSUndoManager使用NSInvocation对象,以便它可以反转命令。本质上,您正在做的是创建一个NSInvocation对象,说:“嘿,如果您要撤消我刚刚所做的操作,请使用这些参数将消息发送到该对象”。您将该NSInvocation对象提供给NSUndoManager,并将该对象添加到一系列不可撤消的操作中。如果用户调用“撤消”,则NSUndoManager只需查找数组中的最新操作,然后调用存储的NSInvocation对象以执行必要的操作。

有关更多详细信息,请参见注册撤消操作


10
对本来很好的答案的一个较小的更正...您必须传递一个指向中的对象的指针setArgument:atIndex:,因此arg赋值实际上应该为[myInvocation setArgument:&myString atIndex:2]
Ryan McCuaig,2009年

60
只是为了澄清Ryan的注释,索引0保留给“ self”,索引1保留给“ _cmd”(有关更多详细信息,请参见链接e.James)。因此,您的第一个参数放在索引2处,第二个参数放在索引3处,等等...
Dave 2010年

4
@haroldcampbell:我们要打什么电话?
e.James 2011年

6
我不明白为什么必须调用setSelector,因为我们已经在mySignature中指定了选择器。
Gleno

6
@Gleno:NSInvocation非常灵活。实际上,您可以设置与方法签名匹配的任何选择器,因此您不必一定使用与创建方法签名相同的选择器。在此示例中,您可以轻松地执行setSelector:@selector(removeObject :),因为它们共享相同的方法签名。
e.James

48

这是一个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];

6
有趣的是,即使invocationWithMethodSignature:使用了初始化程序,您仍然需要调用setSelector:。似乎多余,但我刚刚进行了测试,这是必要的。
ThomasW 2012年

这会保持无限循环运行吗?什么是_cmd
j2emanue 2015年


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.