NSInvocation 傻瓜式的吗?

发布于 2024-07-09 04:47:01 字数 1501 浏览 8 评论 0原文

NSInitation 到底是如何工作的? 有好的介绍吗?

我在理解以下代码(来自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 章,我做到这一点没有任何困难。 但现在 ...

How exactly does NSInvocation work? Is there a good introduction?

I’m specifically having issues understanding how the following code (from Cocoa Programming for Mac OS X, 3rd Edition) works, but then also be able to apply the concepts independently of the tutorial sample. The code:

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

I get what it’s trying to do. (BTW, employees is an NSArray of a custom Person class.)

Being a .NET guy, I try to associate unfamiliar Obj-C and Cocoa concepts to roughly analogous .NET concepts. Is this similar to .NET’s delegate concept, but untyped?

This isn’t 100% clear from the book, so I’m looking for something supplemental from real Cocoa/Obj-C experts, again with the goal that I understand the fundamental concept beneath the simple(-ish) example. I'm really looking to be able to independently apply the knowledge -- up until chapter 9, I was having no difficulty doing that. But now ...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

撧情箌佬 2024-07-16 04:47:01

根据 Apple 的 NSInitation 类参考

NSInitation 是一个静态呈现的 Objective-C 消息,也就是说,它是一个转换为对象的操作。

而且,更详细一点:

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


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

[myArray addObject:myString];

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

首先,您需要准备与 NSMutableArrayaddObject: 选择器一起使用的 NSInspiration 对象:

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 文档了解更多详细信息。

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

[myInvocation invoke];

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

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

有关详细信息,请参阅使用 NSInitation
请参阅如果上述方法不起作用,请使用 NSInitation


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

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

According to Apple's NSInvocation class reference:

An NSInvocation is an Objective-C message rendered static, that is, it is an action turned into an object.

And, in a little more detail:

The concept of messages is central to the objective-c philosophy. Any time you call a method, or access a variable of some object, you are sending it a message. NSInvocation comes in handy when you want to send a message to an object at a different point in time, or send the same message several times. NSInvocation allows you to describe the message you are going to send, and then invoke it (actually send it to the target object) later on.


For example, let's say you want to add a string to an array. You would normally send the addObject: message as follows:

[myArray addObject:myString];

Now, let's say you want to use NSInvocation to send this message at some other point in time:

First, you would prepare an NSInvocation object for use with NSMutableArray's addObject: selector:

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

Next, you would specify which object to send the message to:

[myInvocation setTarget:myArray];

Specify the message you wish to send to that object:

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

And fill in any arguments for that method:

[myInvocation setArgument:&myString atIndex:2];

Note that object arguments must be passed by pointer. Thank you to Ryan McCuaig for pointing that out, and please see Apple's documentation for more details.

At this point, myInvocation is a complete object, describing a message that can be sent. To actually send the message, you would call:

[myInvocation invoke];

This final step will cause the message to be sent, essentially executing [myArray addObject:myString];.

Think of it like sending an email. You open up a new email (NSInvocation object), fill in the address of the person (object) who you want to send it to, type in a message for the recipient (specify a selector and arguments), and then click "send" (call invoke).

See Using NSInvocation for more information.
See Using NSInvocation if the above is not working.


NSUndoManager uses NSInvocation objects so that it can reverse commands. Essentially, what you are doing is creating an NSInvocation object to say: "Hey, if you want to undo what I just did, send this message to that object, with these arguments". You give the NSInvocation object to the NSUndoManager, and it adds that object to an array of undoable actions. If the user calls "Undo", NSUndoManager simply looks up the most recent action in the array, and invokes the stored NSInvocation object to perform the necessary action.

See Registering Undo Operations for more details.

花心好男孩 2024-07-16 04:47:01

下面是 NSIncation 的一个简单示例:

- (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。
  • 创建并填充 NSInitation,调用自身。
  • 将 NSIncation 传递给 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 才能向其发送 NSInitation。 例如,Singleton 对象,或者在应用程序运行期间存在的 AppDelegate。


更新:

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

如果您不将 NSIncation 作为参数传递给 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];

Here's a simple example of NSInvocation in action:

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

When called - [self hello:@"Hello" world:@"world"]; - the method will:

  • Print "Hello world!"
  • Create an NSMethodSignature for itself.
  • Create and populate an NSInvocation, calling itself.
  • Pass the NSInvocation to an NSTimer
  • The timer will fire in (approximately) 1 second, causing the method to be called again with its original arguments.
  • Repeat.

In the end, you'll get a printout like so:

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

Of course, the target object self must continue to exist for the NSTimer to send the NSInvocation to it. For example, a Singleton object, or an AppDelegate which exists for the duration of the application.


UPDATE:

As noted above, when you pass an NSInvocation as an argument to an NSTimer, the NSTimer automatically retains all of the NSInvocation's arguments.

If you are not passing an NSInvocation as an argument to an NSTimer, and plan on having it stick around for a while, you must call its -retainArguments method. Otherwise its arguments may be deallocated before the invocation is invoked, eventually causing your code to crash. Here's how to do it:

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];
智商已欠费 2024-07-16 04:47:01

You could try just using the library here which is much nicer: http://cocoawithlove.com/2008/03/construct-nsinvocation-for-any-message.html

分开我的手 2024-07-16 04:47:01

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

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

https://github.com/clearbrian/NSInitation_Runtime

I build a simple example of calling various method types with NSInvocation.

I had problems calling multiple params using obj_msgSend

https://github.com/clearbrian/NSInvocation_Runtime

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文