OC的Runtime机制之动态方法决议(Dynamic Method Resolution)

如果我们在 ObjectiveC 中向一个对象发送它无法处理的消息,会出现什么情况呢?我们知道发送消息是通过 objc_send(id, SEL, ...) 来实现的,它会首 先在对象的类对象的 cache,methodlist 以及父类对象的 cache,methodlist 中依次查找 SEL 对应 的 IMP;如果没有找到且实现了动态方法决议机制就会进行决议,如果没有实现动态方法决议机制或决议 失败且实现了消息转发机制就会进入消息转发流程,否则程序 crash。也就是说如果同时提供了动态方法 决议和消息转发,那么动态方法决议先于消息转发,只有当动态方法决议依然无法正确决议 selector 的 实现,才会尝试进行消息转发。在前文中,我并没有详细讲解动态方法决议,因此本文将详细介绍之。

动态方法决议

我们先定义一个类:

Objective C 提供了一种名为动态方法决议的手段,使得我们可以在运行时动态地为一个 selector 提供 实现。我们只要实现 +resolveInstanceMethod: 和/或 +resolveClassMethod: 方法,并在其中为指 定的 selector 提供实现即可(通过调用运行时函数 class_addMethod 来添加)。这两个方法都是 NSObject 中的类方法,其原型为:

  • (BOOL)resolveClassMethod:(SEL)name; + (BOOL)resolveInstanceMethod:(SEL)name;

参数 name 是需要被动态决议的 selector ,返回值文档中说是表示动态决议成功与否。但在上面的例子 中(不涉及消息转发的情况下),如果在该函数内为指定的 selector 提供实现,无论返回 YES 还是 NO, 编译运行都是正确的;但如果在该函数内并不真正为 selector 提供实现,无论返回 YES 还是 NO,运 行都会 crash,道理很简单,selector 并没有对应的实现,而又没有实现消息转发。 resolveInstanceMethod 是为对象方法进行决议,而 resolveClassMethod 是为类方法进行决议。


@interface Foo : NSObject

-(void)MissMethod;

- (void)Bar;

@end

#import "Foo.h"

void dynamicMethodIMP(id self, SEL _cmd) {

  NSLog(@" >> dynamicMethodIMP");

}

@implementation  Foo

+ (BOOL)resolveInstanceMethod:(SEL)name {

  NSLog(@" >> Instance resolving %@", NSStringFromSelector(name));

 if (name == @selector(MissMethod)) {

 class_addMethod([self class], name, (IMP)dynamicMethodIMP, "v@:"); return YES;

 }

  return [super  resolveInstanceMethod:name];

}

+ (BOOL)resolveClassMethod:(SEL)name {

  NSLog(@" >> Class resolving %@", NSStringFromSelector(name));

  return [super  resolveClassMethod:name];

}

- (void)Bar {

  NSLog(@" >> Bar() in Foo");

}

//- (void)MissMethod {

//    

//}

@end

然后调用


#import "ViewController.h"

#import "Foo.h"

@interface  ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {

 [super  viewDidLoad];

 Foo *foo = [[Foo alloc] init];

 [foo Bar];

 [foo MissMethod];

}

打印的信息:


**2017-07-02 15:58:53.578584+0800 DynamicMethodDemo[3147:601367] >> Bar() in Foo**

**2017-07-02 15:58:53.578782+0800 DynamicMethodDemo[3147:601367] >> Instance resolving MissMethod**

**2017-07-02 15:58:53.579087+0800 DynamicMethodDemo[3147:601367] >> dynamicMethodIMP**

如果将 [图片上传失败...(image-f485ae-1522657310056)]

//class_addMethod([self class], name, (IMP)dynamicMethod IMP, "v@:");

这行注释掉,虽然会返回YES,但是还是会崩溃,在这里,resolveInstanceMethod 使诈了,它声称成功(返回 YES)决议了 selector,但是并没有真正 提供实现,被编译器发觉而提示相应的错误信息。那它的返回值到底有什么作用呢,在它没有提供真正的 实现,并且提供了消息转发机制的情况下,YES 表示不进行后续的消息转发,返回 NO 则表示要进行后 续的消息转发。

动态方法的内部实现

1,首先判断是否实现了 resolveInstanceMethod,如果没有实现,返回 NULL,进入下一步处理; 2,如果实现了,调用 resolveInstanceMethod,获取返回值;

3,如果返回值为 YES,表示 resolveInstanceMethod 声称它已经提供了 selector 的实现,因此再次 查找 methodlist,如果依然找到对应的 IMP,则返回该实现,否则提示警告信息,返回 NULL,进入下 一步处理;

4,如果返回值为 NO,返回 NULL,进入下一步处理;

加入消息转发内部走 _objc_msgForward


- (void)forwardInvocation:(NSInvocation *)anInvocation {

 SEL name = [anInvocation selector];

  NSLog(@" >> forwardInvocation for selector %@", NSStringFromSelector(name));

 Proxy * proxy = [[Proxy alloc] init];

 if ([proxy respondsToSelector:name]) {

 [anInvocation invokeWithTarget:proxy];

 }

 else {

 [super forwardInvocation:anInvocation];

 }

}

//methodSignatureForSelector:的作用在于为另一个类实现的消息创建一个有效的方法签名,必须实现,并且返回不为空的methodSignature,否则会crash。

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { return [Proxy instanceMethodSignatureForSelector:aSelector];

}

总结:

从上面的示例演示可以看出,动态方法决议是先于消息转发的。

如果向一个 Objective C 对象对象发送它无法处理的消息(selector),那么编译器会按照如下次序进 行处理:

1,首先看是否为该 selector 提供了动态方法决议机制,如果提供了则转到 2;如果没有提供则转到 3; 2,如果动态方法决议真正为该 selector 提供了实现,那么就调用该实现,完成消息发送流程,消息转发

就不会进行了;如果没有提供,则转到 3;

3,其次看是否为该 selector 提供了消息转发机制,如果提供了消息了则进行消息转发,此时,无论消息 转发是怎样实现的,程序均不会 crash。(因为消息调用的控制权完全交给消息转发机制处理,即使消息

转发并没有做任何事情,运行也不会有错误,编译器更不会有错误提示。);如果没提供消息转发机制, 则转到 4;

4,运行报错:无法识别的 selector,程序 crash;

则到现在为止,我们已经了解了消息的全部原理,在这里再整理总结一遍

1.当实例对象调用方法的时候,将方法转换成 id objc_msgSend(id theReceiver, SELtheSelector, ...) (该消息做了动态绑定的所需要的一切工作)

2.首先根据SEL去该类的方法 cache 中查找,如果找到了调到6;

3.如果没有找到,就去该类的方法列表中查找。如果在该类的方法列表中找到了,跳到6,并将 它加入 cache 中缓存起来。根据最近使用原则,这个方法再次调用的可能性很大,缓存起来可以节省下次 调用再次查找的开销。

4.如果在该类的方法列表中没找到对应的 IMP,在通过该类结构中的 super_class 指针在其父类结构的方法列表中去查找,直到在某个父类的方法列表中找到对应的 IMP,跳转到6,并加入 cache 中;

5.如果在自身以及所有父类的方法列表中都没有找到对应的 IMP,首先看是否为该 selector 提供了动态方法决议机制,如果动态方法决议真正为该 selector 提供了实现,那么就调用该实现,完成消息发送流程,消息转发 ,其次看是否为该 selector 提供了消息转发机制,如果提供了消息了则进行消息转发,此时,无论消息 转发是怎样实现的,程序均不会 crash。(因为消息调用的控制权完全交给消息转发机制处理,即使消息 转发并没有做任何事情,运行也不会有错误,编译器更不会有错误提示

6.它首先找到 SEL 对应的方法实现 IMP。因为不同的类对同一方法可能会有不同的实现,所以找到的 方法实现依赖于消息接收者的类型。

7.然后将消息接收者对象(指向消息接收者对象的指针)以及方法中指定的参数传递给方法实现 IMP。

8.最后,将方法实现的返回值作为该函数的返回值返回。

你可能感兴趣的:(OC的Runtime机制之动态方法决议(Dynamic Method Resolution))