OC之消息调用过程

本文详细介绍了Objective-C中动态方法解析的工作原理,包括方法查找、动态绑定、动态方法解析及消息转发的过程。揭示了Objective-C运行时系统如何处理未定义的方法调用,以及开发者如何通过动态方法解析和消息转发来解决这些问题。

Bird * aBird = [[Bird alloc] init];

[aBird fly];

中对 fly 的调用,编译器通过插入一些代码,将之转换为对方法具体实现 IMP 的调用,这个 IMP 是通过 在 Bird 的类结构中的方法链表中查找名称为 fly 的 选标 SEL 对应的具体方法找到实现的。

那么编译器插入了什么代码?如果在方法链表中没有找到对应的 IMP 又会如何 ?
  • 消息函数 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]; }
forwardInvocation:方法就像一个不能识别的消息的分发中心,将这些消息转发给不同接收对象。 
该方法所?供是将不同的对象链接到消息链的能力 。

上面我演示了一个消息转发的示例,下面我把动态方法决议部分去除,把消息转发部分添加进来: 

 

@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;

 

转载于:https://www.cnblogs.com/H7N9/p/4893323.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值