Bird * aBird = [[Bird alloc] init];
[aBird fly];
中对 fly 的调用,编译器通过插入一些代码,将之转换为对方法具体实现 IMP 的调用,这个 IMP 是通过 在 Bird 的类结构中的方法链表中查找名称为 fly 的 选标 SEL 对应的具体方法找到实现的。
- 消息函数 obj_msgSend:
编译器会将消息转换为对消息函数 objc_msgSend 的调用,该函数有两个主要的参数:消息接收者 id 和
消息对应的方法选标 SEL, 同时接收消息中的任意参数: id objc_msgSend(id theReceiver, SEL theSelector, ...)
如上面的消息 [aBird fly]会被转换为如下形式的函数调用: objc_msgSend(aBird, @selector(fly));
该消息函数做了动态绑定所需要的一切工作:
1,它首先找到 SEL 对应的方法实现 IMP。因为不同的类对同一方法可能会有不同的实现,所以找到的方法实现依赖于消息接收者的类型。
2, 然后将消息接收者对象(指向消息接收者对象的指针)以及方法中指定的参数传递给方法实现 IMP。
3, 最后,将方法实现的返回值作为该函数的返回值返回。
在方法中可以通过 self 来引用消息接收者对象,通过选标_cmd 来引用方法本身。在下面的例 子中,_cmd 指的是 strange 方法,self 指的收到 strange 消息的对象。
1  2 - strange { 3 id target = getTheReceiver(); 4 SEL method = getTheMethod(); 5 if (target == self || mothod == _cmd) 6 return nil; 7 return [target performSelector:method]; 8 }
- 查找 IMP 的过程:
前面说了,objc_msgSend 会根据方法选标 SEL 在类结构的方法列表中查找方法实现 IMP。这里头有一 些文章,我们在前面的类结构中也看到有一个叫 objc_cache *cache 的成员,这个缓存为?高效率而存在 的。每个类都有一个独立的缓存,同时包括继承的方法和在该类中定义的方法。
下面来剖析一段苹果官方运行时源码:
1 static Method look_up_method(Class cls, SEL sel, 2 BOOL withCache, BOOL withResolver) 3 { 4 Method meth = NULL; 5 if (withCache) { 6 meth = _cache_getMethod(cls, sel, &_objc_msgForward_internal); 7 if (meth == (Method)1) { 8 // Cache contains forward:: . Stop searching. 9 return NULL; 10 } 11 } 12 if (!meth) meth = _class_getMethod(cls, sel); 13 if (!meth && withResolver) meth = _class_resolveMethod(cls, sel); 14 return meth; 15 }
通过分析上面的代码,可以看到,查找时:
1,首先去该类的方法 cache 中查找,如果找到了就返回它;
2,如果没有找到,就去该类的方法列表中查找。如果在该类的方法列表中找到了,则将 IMP 返回,并将 它加入 cache 中缓存起来。根据最近使用原则,这个方法再次调用的可能性很大,缓存起来可以节省下次 调用再次查找的开销。
3,如果在该类的方法列表中没找到对应的 IMP,在通过该类结构中的 super_class 指针在其父类结构的方法列表中去查找,直到在某个父类的方法列表中找到对应的 IMP,返回它,并加入 cache 中;
4,如果在自身以及所有父类的方法列表中都没有找到对应的 IMP,则看是不是可以进行动态方法决议(后面继续讲述这个话题);
5,如果动态方法决议没能解决问题,进入下面要讲的消息转发流程。
- 动态方法决议
如果我们在 Objective C 中向一个对象发送它无法处理的消息,会出现什么情况呢?
1 @interface Foo : NSObject 2 -(void)Bar; 3 @end
4 @implementation Foo 5 -(void)Bar 6 { 7 NSLog(@" >> Bar() in Foo"); 8 } 9 @end 10 11 #import "Foo.h" 12 int main (int argc, const char * argv[]) { 13 @autoreleasepool { 14 Foo * foo = [[Foo alloc] init]; 15 [foo Bar]; 16 [foo MissMethod]; 17 [foo release]; } 18 return 0; }
在编译时,XCode 会?示警告:
Instance method '-MissMethod' not found (return type defaults to 'id')
如果,我们忽视该警告运行之,一定会 crash:
>> Bar() in Foo
-[Foo MissMethod]: unrecognized selector sent to instance 0x10010c840
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Foo MissMethod]: unrecognized selector sent to instance 0x10010c840'
*** Call stack at first throw:
......
terminate called after throwing an instance of 'NSException'
下划线部分就是造成 crash 的原因:对象无法处理 MissMethod 对应的 selector,也就是没有相应的实现。
Objective C ?供了一种名为动态方法决议的手段,使得我们可以在运行时动态地为一个 selector ?供 实现。我们只要实现 +resolveInstanceMethod: 和/或 +resolveClassMethod: 方法,并在其中为指 定的 selector ?供实现即可(通过调用运行时函数 class_addMethod 来添加)。这两个方法都是 NSObject 中的类方法,其原型为:
+ (BOOL)resolveClassMethod:(SEL)name;//为类方法进行决议
+ (BOOL)resolveInstanceMethod:(SEL)name;//为对象方法进行决议
参数 name 是需要被动态决议的 selector;返回值文档中说是表示动态决议成功与否,
经验证,返回值在它没有?提供真正的实现,并且?供了消息转发机制的情况下,YES 表示不进行后续的消息转发,返回 NO 则表示要进行后续的消息转发。
用动态方法决议手段来修改上面的代码
1 #import "Foo.h" 2 #include <objc/runtime.h> 3 void dynamicMethodIMP(id self, SEL _cmd) 4 { 5 NSLog(@" >> dynamicMethodIMP"); 6 } 7 8 @implementation Foo 9 -(void)Bar 10 { 11 NSLog(@" >> Bar() in Foo"); 12 } 13 14 + (BOOL)resolveInstanceMethod:(SEL)name 15 { 16 NSLog(@" >> Instance resolving %@", NSStringFromSelector(name)); 17 if (name == @selector(MissMethod)) { 18 class_addMethod([self class], name, (IMP)dynamicMethodIMP, "v@:");
return YES; 19 } 20 return [super resolveInstanceMethod:name]; 21 } 22 23 + (BOOL)resolveClassMethod:(SEL)name 24 { 25 NSLog(@" >> Class resolving %@", NSStringFromSelector(name)); 26 return [super resolveClassMethod:name]; 27 } 28 @end
通过调用
class_addMethod([self class], name, (IMP)dynamicMethodIMP, "v@:");
就能在运行期动态地为 name 这个 selector 添加实现
- 消息转发:
Objective-C 运行时系统在抛出错误之前, 会给消息接收对象发送一条特别的消息 forwardInvocation 来通知该对象,该消息的唯一参数是个 NSInvocation 类型的对象——该对象封装了原始的消息和消息的参数。
可以实现 forwardInvocation:方法来对不能处理的消息做一些默认的处理,也可以将消息转发给其他对 象来处理,而不抛出错误。
当一个对象由于没有相应的方法实现而无法响应某消息时,运行时系统将通过 forwardInvocation:消息通知该对象。每个对象都从 NSObject 类中继承了 forwardInvocation:方法。然而,NSObject 中的方法实现只是简单地调用了 doesNotRecognizeSelector:。通过实现我们自己的 forwardInvocation:方法,我们可以在该方法实现中将 消息转发给其它对象。
要转发消息给其它对象,forwardInvocation:方法所必须做的有:
1,决定将消息转发给谁
2,将消息和原来的参数一块转发出去。
消息可以通过 invokeWithTarget:方法来转发:
- (void) forwardInvocation:(NSInvocation *)anInvocation { if ([someOtherObject respondsToSelector:[anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject]; else [super forwardInvocation:anInvocation]; }
上面我演示了一个消息转发的示例,下面我把动态方法决议部分去除,把消息转发部分添加进来:
@interface Proxy : NSObject -(void)MissMethod; @end @implementation Proxy -(void)MissMethod { NSLog(@" >> MissMethod() called in Proxy."); } @end // Foo @interface Foo : NSObject -(void)Bar; @end @implementation Foo - (void)forwardInvocation:(NSInvocation *)anInvocation { SEL name = [anInvocation selector]; NSLog(@" >> forwardInvocation for selector %@", NSStringFromSelector(name)); Proxy * proxy = [[[Proxy alloc] init] autorelease]; if ([proxy respondsToSelector:name]) { [anInvocation invokeWithTarget:proxy]; } else { [super forwardInvocation:anInvocation]; } } - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { return [Proxy instanceMethodSignatureForSelector:aSelector]; } -(void)Bar { NSLog(@" >> Bar() in Foo."); } @end
运行测试代码,输出如下:
>> Bar() in Foo.
>> forwardInvocation for selector MissMethod >> MissMethod() called in Proxy.
- 总结
如果向一个 Objective C 对象对象发送它无法处理的消息(selector),那么编译器会按照如下次序进 行处理:
1,首先看是否为该 selector ?供了动态方法决议机制,如果?供了则转到 2;如果没有?供则转到 3;
2,如果动态方法决议真正为该 selector ?供了实现,那么就调用该实现,完成消息发送流程,消息转发
就不会进行了;如果没有?供,则转到 3;
3,其次看是否为该 selector ?供了消息转发机制,如果?供了消息了则进行消息转发,此时,无论消息 转发是怎样实现的,程序均不会 crash。(因为消息调用的控制权完全交给消息转发机制处理,即使消息
转发并没有做任何事情,运行也不会有错误,编译器更不会有错误?示。);如果没?供消息转发机制, 则转到 4;
4,运行报错:无法识别的 selector,程序 crash;