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.最后,将方法实现的返回值作为该函数的返回值返回。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,755评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,305评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,138评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,791评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,794评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,631评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,362评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,264评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,724评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,900评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,040评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,742评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,364评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,944评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,060评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,247评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,979评论 2 355

推荐阅读更多精彩内容