iOS 底层原理之消息动态决议

82 阅读3分钟

引言

当我们在上层调用一个方法时,底层调用了objc_msgSend(receiver, self)进入汇编流程进行快速缓存中查找,当找不到时便会跳转到LookUpImpOrForward函数,该函数内部首先实现了类-> 父类 -> ... -> NSObject -> nil以及类的元类 -> 父元类 -> ... -> 根元类 -> NSObject -> nil,并使用二分法找到sel对应的imp实例方法按照类的继承链找下去(类方法找到元类继承链找下去),直到找到nil找不到,则会进入方法的动态决议中

  1. 下图便是方法动态决议的入口,从注释中可以看出这个是单例方法,为什么是个单例方法呢? behavior = 3, LOOKUP_RESOVLER = 2,那么behavior = 3 ^ 2 = 0011 ^ 0010 = 0001 = 1,只要进入一次behavior = 1,等下次再进入时behavior & LOOKUP_RESOVLER = 1 & 2 = 0001 & 0010 = 0,一直为false,所以这里只会走一次。进入resolveMethod_locked函数

image.png

image.png

resoveMethod_locked函数分为两个流程,一是对象方法的动态决议,二是对象方法的动态决议

对象方法动态决议

下图便是resoveInstanceMethod函数的实现

image.png

红框标识出来代码等价于

// 1. 发送一个objc_msgSend消息
objc_msgSend(inst, @selector(resolveInstanceMethod:),sel);
// 2. 查找实现
IMP imp = loopUpImpOrNilTryCache(inst, @selector(resolveInstanceMethod:), self)

_lookUpImpTryCache内部实现

①首先通过cache_getImp(cls, sel) 汇编进行快速查找resloveInstanceMethod:函数

  • 如果找到了,此时imp不为空,并且imp != _objc_msgForward_impcache则,返回imp

  • 如果在汇编中找不到resolveInstanceMethod:的实现,则会进入②

② 进入lookUpImpOrForward()慢速查找resolveInstanceMthod:

image.png

类方法动态决议

① 首先使用resolveClassMethod元类调用resoveClassMethod:函数 image.png 上图代码等价于

// 使用objc_msgSend发出消息[nonmate resolveClassMethod:sel]
objc_msgSend(nonmate, @selector(resolveClassMethod:), sel);
// 在元类的缓存中查找是否有resolveClassMethod: 函数,该流程和对象动态决议中lookUpImpOrNilTryCache一致
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);

②如果在元类中没有找到resloveClassMethod:函数,则由调用resoveInstanceMethod:函数,并查找是否有实现。该流程和对象动态决议流程一致,不再赘述。

应用

main.m中调用DXJTeacher对象方法sayHello3,调用类方法sayHello4

image.png 这两个方法都是在DXJTeacher的父类DXJPerson仅声明未实现

image.png 如果此时运行起来,必然会报错:-[DXJTeacher sayHello3]: unrecognized selector sent to instance 0x100b1c340,当然sayHello4也会报错,只是没有运行到那一步而已,这是没有找到方法,我们知道方法的查找链是类 -> 父类 -> ... -> 根类 -> nil,那此时如果我们给根类写一个分类用来处理未实现的方法,如下图

image.png

扩展之_objc_msgForward_impcache

上篇文章研究了方法的慢速查找LookUpImpOrForward,当该函数整个流程都走完方法找不到时会执行const IMP forward_imp = (IMP)_objc_msgForward_impcache,此时全局搜索_objc_msgForward_impcache 函数,发现进入了汇编流程,如下图

image.png

image.png

全局查找__objc_forward_handler,没有找到实现,去掉一个_再次全局搜索,定位到了objc_runtime文件,如下图:

image.png

objc_defaultForwardHandler的内部打印,就是我们平时调用一个未实现的方法时会报的错提示,如下图。从源码中可以看到所谓的+方法 和 -方法 是通过判断当前的self是否是元类决定的,无论是实例方法还是类方法底层都是函数,只不过实例方法放在了中,类方法放在了元类中用来区分该函数实例方法还是类方法

image.png