NSInvocation 傻瓜?

Posted

技术标签:

【中文标题】NSInvocation 傻瓜?【英文标题】:NSInvocation for Dummies? 【发布时间】:2008-11-24 04:17:49 【问题描述】:

NSInvocation 究竟是如何工作的?有好的介绍吗?

我在理解以下代码(来自 Cocoa Programming for Mac OS X,第 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 是自定义 Person 类的 NSArray。)

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

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

提前致谢!

【问题讨论】:

【参考方案1】:

根据Apple's NSInvocation class reference:

NSInvocation是一个Objective-C消息呈现为静态,也就是说,它是一个动作变成了一个对象。

还有,一点更详细:

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


例如,假设您想将字符串添加到数组中。您通常会按如下方式发送addObject: 消息:

[myArray addObject:myString];

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

首先,您将准备一个 NSInvocation 对象以与 NSMutableArrayaddObject: 选择器一起使用:

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's documentation。

此时,myInvocation 是一个完整的对象,描述了一个可以发送的消息。要实际发送消息,您会调用:

[myInvocation invoke];

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

把它想象成发送电子邮件。您打开一封新电子邮件(NSInvocation 对象),填写您要将其发送到的人(对象)的地址,输入收件人的消息(指定 selector 和参数),然后点击“发送”(致电invoke)。

请参阅Using NSInvocation 了解更多信息。 如果上述方法不起作用,请参阅Using NSInvocation。


NSUndoManager 使用NSInvocation 对象,以便它可以反转 命令。本质上,您所做的是创建一个NSInvocation 对象来表示:“嘿,如果您想撤消我刚刚所做的事情,请使用这些参数将此消息发送给该对象”。您将NSInvocation 对象赋予NSUndoManager,然后它将该对象添加到可撤消操作的数组中。如果用户调用“撤消”,NSUndoManager 只需在数组中查找最近的操作,并调用存储的NSInvocation 对象来执行必要的操作。

详情请见Registering Undo Operations。

【讨论】:

对一个很好的答案的一个小修正......你必须传递一个指向setArgument:atIndex:中对象的指针,所以arg赋值实际上应该是[myInvocation setArgument:&myString atIndex:2] 只是为了澄清 Ryan 的注释,索引 0 为“self”保留,索引 1 为“_cmd”保留(有关详细信息,请参阅链接 e.James 发布)。所以你的第一个参数被放置在索引 2,第二个参数在索引 3,等等...... @haroldcampbell:我们该叫什么? 我不明白为什么我们必须调用 setSelector,因为我们已经在 mySignature 中指定了选择器。 @Gleno:NSInvocation 非常灵活。您实际上可以设置与方法签名匹配的任何选择器,因此您不必使用与创建方法签名相同的选择器。在此示例中,您可以轻松地执行 setSelector:@selector(removeObject:),因为它们共享相同的方法签名。【参考方案2】:

下面是一个简单的 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];

【讨论】:

有趣的是,即使使用了invocationWithMethodSignature: 初始化器,您仍然需要调用setSelector:。好像是多余的,不过我刚刚测试了一下,是有必要的。 这是否一直在无限循环中运行?什么是_cmd【参考方案3】:

您可以尝试在这里使用更好的库:http://cocoawithlove.com/2008/03/construct-nsinvocation-for-any-message.html

【讨论】:

【参考方案4】:

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

我在使用 obj_msgSend 调用多个参数时遇到问题

https://github.com/clearbrian/NSInvocation_Runtime

【讨论】:

以上是关于NSInvocation 傻瓜?的主要内容,如果未能解决你的问题,请参考以下文章

NSInvocation的基本使用

NSTimer 和 NSInvocation,定时器不触发

NSInvocation 理解

NSInvocation:对象没有实现 methodSignatureForSelector

Objective-C中NSInvocation的使用

如何安全地测试一个方法是不是可以通过 NSInvocation 被调用