为傻瓜祈祷?

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是一个定制的 Person类的 NSArray。)

成为一个。NET 的家伙,我试图把不熟悉的 Obj-C 和 Cocoa 概念大致相似。NET 概念。这和。NET 的委托概念,但是非类型化?

这在书中并不是100% 清楚的,所以我正在从真正的 Cocoa/Obj-C 专家那里寻找一些补充,同样是为了理解简单(- ish)示例下的基本概念。我真的希望能够独立应用这些知识——直到第9章,我在这方面没有任何困难。但现在..。

先谢谢你!

60917 次浏览

根据 Apple 的 NSInvation 类引用:

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指出这一点,请参阅 苹果的文档了解更多细节。

此时,myInvocation是一个完整的对象,描述可以发送的消息。为了真正发送信息,你可以打电话:

[myInvocation invoke];

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

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

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


NSUndoManager使用 NSInvocation对象以便能够执行 倒车命令。实际上,您正在做的是创建一个 NSInvocation对象来说: “嘿,如果您想撤消我刚才所做的,发送这个消息到该对象,使用这些参数”。您将 NSInvocation对象赋予 NSUndoManager,它将该对象添加到一个可撤销操作的数组中。如果用户调用“ Undo”,NSUndoManager只需查找数组中最新的操作,并调用存储的 NSInvocation对象来执行必要的操作。

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

下面是运行中的 NSInvation 的一个简单示例:

- (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。
  • 创建并填充一个 NSInvation,调用它自己。
  • 将 NSInvation 传递给 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 才能将 NSInvation 发送给它。例如,辛格尔顿对象或在应用程序存在期间存在的应用程序委托。


更新:

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

如果没有将 NSInvation 作为参数传递给 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];

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

我在使用 obj _ msgsend 调用多个参数时遇到了问题

Https://github.com/clearbrian/nsinvocation_runtime