本文由快学吧个人写作,以任何形式转载请表明原文出处
一、资料准备
对应mac的版本是11.1。可根据自己的系统版本挑选可以进行调试的源码。
二、思路
- 在缓存(包括超类的缓存)和继承链中都找不到方法的imp的时候,
lookUpImpOrForward源码中提到了动态决议。 - 动态决议会将sel和imp存入cls的缓存中。所以动态决议会给sel找一个imp。
- 动态决议做了什么,才能给sel找imp?
三、动态决议的源码
- 从
lookUpImpOrForward中可以找到 :
resolveMethod_locked是执行了动态协议的返回结果,看源码 :
- 一共4个没看过的函数。全部分开看。
四、实例方法的动态决议
1. resolveInstanceMethod源码 :
总结 :
resolveInstanceMethod里面,先检查了调用方法的类是不是继承于NSObject的,如果是可以继续进行,如果不是,直接return。向调用方法的类(cls)通过objc_msgSend发送了一条信息,让cls调用+(BOOL)resolveInstanceMethod方法。(在这个方法中我们可以给sel做一些操作,比如给sel绑定一个imp。)
再次查找sel有没有在cls或者cls继承链上实现。如果有,拿到imp。如果没有,imp = nil。
2. lookUpImpOrNilTryCache源码 :
_lookUpImpTryCache源码 :
总结 :
查找缓存中的imp。
(1). 如果自己的缓存中有imp,直接获取,并且直接去done:中直接返回imp,不再执行下面的判断。
(2). 如果自己缓存中没有imp,查找共享缓存,获取imp。
(3). 如果共享缓存没有imp,返回lookUpImpOrForward流程的返回结果。
3. 举例
看到这里还是不知道实例方法的动态决议有什么用,或者说上面的源码都说了什么。其中的重点就是提到了一个方法 : +resolveInstanceMethod方法。
(1). 找一下resolveInstanceMethod方法 :
这是一个NSObject类的方法。也就是说,只要是继承于NSObject的类,都可以使用。
(2). 举例代码 :
- 创建一个JDPerson类,类中有一个实例方法:
-(void)zhuanQian;,并且不写它的实现。
- 在
JDPerson.m中实现resolveInstanceMethod方法如下 :
- 执行代码 :
总结 :
核心就是
+ (BOOL)resolveInstanceMethod:(SEL)sel中进行了sel和imp的绑定,将没有方法实现的sel重新绑定一个imp,绑定的方法就是class_addMethod。动态协议完成sel和imp绑定之后(相当于源码中
resolveInstanceMethod中的bool resolved = msg(cls, resolve_sel, sel);)之后,再lookUpImpOrNilTryCache,就会通过缓存或者lookUpImpOrForward找到sel对应的imp。
五、类方法的动态决议
1. resolveClassMethod
总结 :
resolveClassMethod函数里面先检查元类是不是继承于NSObject的,如果是继承于NSPbject的,那么继续下面的流程,如果不是继承于NSObject的,那么直接return。获取元类的普通类。因为可能在cls这个元类的普通类中实现(重写)了
+resolveClassMethod:方法。通过objc_msgSend,让元类的普通类调用
+resolveClassMethod:方法。(可以在+resolveClassMethod:做一些操作,比如给sel绑定一个imp)。查找cls和cls继承链,获取sel的imp,如果sel有imp,则返回imp,如果没有imp,返回的是nil,imp=nil。
解释一下为什么非要有一个获取元类的普通类的操作 :
因为可能在cls这个元类的普通类中实现(重写)了
+resolveClassMethod:方法。举个例子 : 假设A类的元类叫A元类,并且是A类中重写了
+resolveClassMethod:方法,而不是在NSObject或者NSObject的分类中重写的+resolveClassMethod:方法。如果objc_msgSend的消息接收者是A元类,让A元类去调用+resolveClassMethod:方法,那么A元类只会在 : A元类本身中、A元类的父类中和NSObject中去查找+resolveClassMethod:方法的实现,而不会找到A类的+resolveClassMethod:方法实现。最终找到的只会是NSObject中的+resolveClassMethod:方法实现,那是个默认的方法实现,里面直接返回NO,不会做其他的操作。也就等于,我们在A类中重写的+resolveClassMethod:方法没有被找到,也没有被调用。
2. 类的动态决议为什么会多一个步骤:判断,是否要调用resolveInstanceMethod
先看我的注释。重点在resolveInstanceMethod中的bool resolved = msg(cls, resolve_sel, sel);这一行。
因为现在cls是元类,元类中本身是没有+resolveInstanceMethod:方法的,发消息给元类,让元类调用这个方法,肯定调用的不是元类本身的这个方法,那么调用的就应该是父类的。
元类的父类是根元类,也就是NSObject元类,所以最终这个bool resolved = msg(cls, resolve_sel, sel);代码,是元类cls调用到了NSObject中的+resolveInstanceMethod:方法。
如果想给sel绑定一个imp,+resolveClassMethod()没有做到,会进入到这个if判断,那么就应该在NSObject或者NSObject的分类中重写+resolveInstanceMethod:方法,在重写中完成给sel绑定一个imp。
3. 举例
如何用resolveClassMethod解决一些问题。举个简单的例子。
(1). 找一下resolveClassMethod方法 :
(2). 举例代码(在resolveClassMethod中为sel绑定imp)
- 在上面创建过的JDPerson中添加一个类方法 :
+ (void)xueXi;。并且不要实现它。然后让JDPerson调用+ (void)xueXi;。
- 在
JDPerson.m中实现resolveClassMethod方法 :
- 执行代码 :
备注 :
这个例子中的第2步,就是解释了 : 五--->1--->总结--->5,中的为什么非要获取元类的普通类。如果不获取普通类,直接用元类本身,那么我在JDPerson.m中实现的
resolveClassMethod是不会被找到的。因为JDPerson元类只会找NSObject元类和NSObject类,而不会找JDPerson类中的resolveClassMethod方法实现。
(3). 举例代码(不在resolveClassMethod中为sel绑定imp)
如果不在resolveClassMethod方法中绑定类方法的sel和imp,那么就会进入这里 :
看下注释,这个举例就是针对 : 五--->2的举例。
- JDPerson.m中的仅保留如下代码。在JDPerson.h中还是保留
+ (void)xueXi;。并且不要实现它。然后让JDPerson调用+ (void)xueXi;。
- 创建NSObject的分类,NSObject的分类中实现
resolveInstanceMethod方法 :
- 执行代码 :
4. lookUpImpOrForwardTryCache
- 这是动态解析的最后一步,看源码 :
-
会发现和 : 四--->2中的
lookUpImpOrNilTryCache源码核心是一样的,都是_lookUpImpTryCache。 -
为什么一样的核心源码还要写不同的两个函数?
因为两个函数给_lookUpImpTryCache传的最后一个参数:behavior是不一样的。
behavior的作用是在上一节的
lookUpImpOrForward中控制动态决议只执行一次的。不然从动态决议的入口这边再进来,就会一直在lookUpImpOrForward和动态决议的中间循环。
六、总结
只有继承NSObject的类,才适用本章的内容。
实例方法的动态决议 :
(1) 会从当前类开始并沿着继承链开始查找并调用
resolveInstanceMethod,resolveInstanceMethod是NSObject默认实现的,默认返回值是NO。(2) 我们可以在当前类或者当前类的超类(一直到NSObject的分类)中重写
resolveInstanceMethod方法。可以在里面自己写一些逻辑,用于给没有imp的sel重新绑定一个imp,防止出现因找不到方法实现而出现的崩溃。
- 类方法的动态决议 :
分为两种方法 :
(1) 一种是
resolveClassMethod。会从当前类开始并沿着继承链开始查找并调用resolveClassMethod,resolveClassMethod也是NSObject默认实现的,默认返回值是NO。我们可以在当前类或者当前类的超类(一直到NSObject的分类)中重写
resolveClassMethod方法。可以在里面自己写一些逻辑,用于给没有imp的sel重新绑定一个imp,防止出现因找不到方法实现而出现的崩溃。(2) 一种是
resolveInstanceMethod。针对类方法的动态决议,只有在进行过resolveClassMethod,但是类方法的sel依然找不到imp的情况下,才会被调用。
resolveInstanceMethod为类方法的sel绑定imp只能在NSObject的分类中重写绑定逻辑,才有用。