140

具体是如何NSInvocation工作的?有好的介绍吗?

我在理解以下代码(来自Cocoa Programming for Mac OS X, 3rd Edition)如何工作时特别有问题,但随后也能够独立于教程示例应用这些概念。编码:

- (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 章,我都没有遇到任何困难。但现在 ...

提前致谢!

4

4 回答 4

288

根据Apple 的 NSInvocation 类参考

AnNSInvocation是一个渲染为静态的 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 的文档。

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

[myInvocation invoke];

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

把它想象成发送电子邮件。您打开一个新的电子邮件(NSInvocation对象),填写您要发送的人(对象)的地址,为收件人输入一条消息(指定 aselector和参数),然后单击“发送”(调用invoke)。

有关更多信息,请参阅使用 NSInvocation。如果上述方法不起作用,请参阅使用 NSInvocation 。


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

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

于 2008-11-24T05:01:06.030 回答
49

下面是一个简单的 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"];- 该方法将:

  • 打印“你好世界!”
  • 为自己创建一个 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];
于 2010-07-11T21:57:27.807 回答
6

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

于 2010-02-02T00:24:00.830 回答
0

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

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

https://github.com/clearbrian/NSInvocation_Runtime

于 2014-05-28T15:33:26.040 回答