NSInvocation是一个非常特别的对象,这个对象将一个对象的消息(通常在其他语言中我们称之为函数)封装成一个对象。然后在不同的对象之间进行传递。
一个比较典型的场景是说,当一个对象在被调用一个他并不是别的消息(函数)时,他会查找他的forwardInvocation方法,如果实现了forwardInvocation方法,那么在这个方法当中,会将不能够被识别的消息,封装为NSInvocation对象,然后发送下去。
因此,NSInvocation的比较典型的应用就是消息的转发。或者说是函数功能的转发。
补充一下,对于一个对象,调用函数的方法,其实有如下方法:
1、【object fun】;
2、【object performselect:withObject】;这种方法可以完成简单的调用关系,对于参数》2时,就需要做额外的处理。那么方法3就比较简单
3、NSInvocation方法。
关于3的实例:
1. #import <Foundation/Foundation.h>
2. #import "MyClass.h"
3.
4. int main (int argc, const char * argv[])
5. {
6.
7. NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
8.
9. MyClass *myClass = [[MyClass alloc] init];
10. NSString *myString = @"My string";
11.
12. //普通调用
13. NSString *normalInvokeString = [myClass appendMyString:myString];
14. NSLog(@"The normal invoke string is: %@", normalInvokeString);
15.
16. //NSInvocation调用
17. SEL mySelector = @selector(appendMyString:);
18. NSMethodSignature * sig = [[myClass class]
19. instanceMethodSignatureForSelector: mySelector];
20.
21. NSInvocation * myInvocation = [NSInvocation invocationWithMethodSignature: sig];
22. [myInvocation setTarget: myClass];
23. [myInvocation setSelector: mySelector];
24.
25. [myInvocation setArgument: &myString atIndex: 2];
26.
27. NSString * result = nil;
28.
这里说明一下[myInvocation setArgument: &myStringatIndex: 2];为什么index从2开始
文档中的说明
Indices 0 and 1 indicate the hidden arguments self and_cmd, respectively; you should set these values directly with the setTarget:and setSelector: methods. Use indices 2 and greater for the arguments normallypassed in a message.意思就是0和1是隐藏参数,而这两个参数是要在setTarget和setSelector设置的,所以我们调用方法中的参数就要从2开始了,如果有多个参数,那么就依次递增。
那么他的比较典型的应用,就是来源于对于撤销和重做的功能,在NSUndoManager当中,其实提供了两个栈,一个是undo的栈,另一个是redo的栈,然后用户在进行了对应的操作之后,会将该操作的逆操作记录到undo栈当中,这样当用户触发undo时,undomanager,或者叫做undo管理器会调用栈中的方法,从而实现undo操作,然后undo管理器,会将undo操作放到redo栈当中,等待用户出发redo操作。
实例:
- (void) walkLeft
{
position = position + 10;
[[undoManager prepareWithInvocationTarget:self] walkRight];
[self showTheChangesToThePostion];
}
prepareWithInvocationTarget:方法记录了target并返回UndoManager,然后UndoManager重载了forwardInvocation方法,也就将walkRight方法的Invocation添加到undo栈中了。
- (void) walkRight
{
position = position - 10;
[[undoManager prepareWithInvocationTarget:self] walkLeft];
[self showTheChangesToThePostion];
}
消息转发
分类: iPhone2011-09-01 11:36 166人阅读 评论(0) 收藏 举报
在前面的objc_msgSend()函数的最后,我们总结了Objective-C 的方法调用过程,在最后一步我们说如果一路找下来还是没有找到调用的方法,就会报告错误,实际上这里有个细节,那就是最终找不到调用的方法的时候,系统会调用-(void)forwardInvocation: (NSInvocation*)invocation 方法,如果你的对象没有实现这个方法,就调用NSObject 的forwardInvocation 方法,那句不能识别消息的错误,实际就是NSObject 的forwardInvocation 抛出来的异常。我们这里告诉你这个系统内部的实现过程,实际是要告诉你,你可以覆盖forwardInvocation方法,来改变NSObject 的抛异常的处理方式。譬如:你可以把A 不能处理的消息转发给B去处理。
NSInvocation 是一个包含了receiver、selector 的对象,也就是它包含了向一个对象发送消息的所有元素:对象、方法名、参数序列,你可以调用NSInvocation的invoke 方法将这个消息激活。
main.m:
[cpp] view plaincopy
1. int main (int argc, const char * argv[]) {
2. NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
3. Person *person=[[Person alloc] init];
4. person.name=@"Jetta";
5. [person fly];
6. [person release];
7. [pool drain];
8. return 0;
9. }
这里我们调用了一个Person 中不存在的方法fly。
Bird.m:
[cpp] view plaincopy
1. #import "Bird.h"
2. @implementation Bird
3. -(void) fly{
4. printf("Bird Can fly!");
5. }
6. @end
7. Person.m
8. @implementation Person
9. @synthesize name;
10. @synthesize weight;
11. -(NSMethodSignature*) methodSignatureForSelector:(SEL)selector{
12. //首先调用父类的方法
13. NSMethodSignature *signature=
14. [super methodSignatureForSelector: selector];
15. //如果当前对象无法回应此selector,那么selector构造的方法签名必然为nil
16. if (!signature) {
17. //首先判断Bird的实例是否有能力回应此selector
18. if ([Bird instancesRespondToSelector:selector]) {
19. //获取Bird的selector的方法签名对象
20. signature=[Bird instanceMethodSignatureForSelector:
21. selector];
22. }
23. }
24. return signature;
25. }
26. -(void) forwardInvocation: (NSInvocation*) invocation{
27. //首先验证Bird是否有能力回应invocation中包含的selector
28. if ([Bird instancesRespondToSelector:[invocation selector]]) {
29. //创建要移交消息响应权的实例bird
30. Bird *bird=[Bird new];
31. //激活invocation中的消息,但是消息的响应者是bird,而不是默认的self。
32. [invocation invokeWithTarget:bird];
33. }
34. }
35. -(void) dealloc{
36. [self setName:nil];
37. [super dealloc];
38. }
39. @end
下面我们来详细分析一下如果你想把不能处理的消息转发给其他的对象,需要经过哪个几个步骤:
A.首先,你要覆盖NSObject中的methodSignatureForSelector方法。这是因为你如果想把消息fly从Person转发给Bird处理,那么你必须将NSInvocation中包含的Person的fly的方法签名转换为Bird的fly的方法签名,也就是把方法签名纠正一下。
由此,你也看出来NSInvocation的创建,内部使用了两个对象,一个是receiver,一个是NSMethodSignature,而NSMethodSignature是由SEL创建的。NSInvocation确实存在一个类方法invocationWithMethodSignature返回自身的实例。
B.然后我们覆盖forwardInvocation方法,使用的不是invoke方法,而是invokeWithTarget方法,
也就是把调用权由self转交给bird。
实际上消息转发机制不仅可以用来处理找不到方法的错误,你还可以变相的实现多继承。假如我们的Person 想要拥有Bird、Fish 的所有功能,其实你可以尽情的用Person 的实例调用Bird、Fish 的方法,只要在Person 的forwardInvocation 里,把消息的响应权转交给Bird 或者Fish 的实例就可以了。不过这种做法实在有点儿BT,除非万不得已,否则千万不要这么做,但是你也从这里能够看出来Objective-C 这种语言有多么的灵活、强大,这是JAVA 所完全不能相比的