OC底层原理14-2:objc_msgSend方法列表查找(快速查找)汇编分析
前言
上一章我们了解了 objc_msgSend消息发送慢速查找流程
即 缓存CacheLookup查找
这一章我们来学习 objc_msgSend消息发送慢速查找流程
即 MethodTableLookup(即查询方法列表)
方法列表查找汇编分析
在 缓存CacheLookup查找
过程中,如果没有找到方法实现,无论是走到 CheckMiss
还是 JumpMiss
,最终都会走到 __objc_msgSend_uncached
汇编函数
- 在
objc-msg-ram64.s
文件中查找__objc_msgSend_uncached
的汇编实现,其中的核心是MethodTableLookup(即查询方法列表)
,其源码如下:
__objc_msgSend_uncached源码
1 | STATIC_ENTRY __objc_msgSend_uncached |
- 搜索
MethodTableLookup
的汇编实现,其中的核心是_lookUpImpOrForward
,汇编源码实现如下:
1 | .macro MethodTableLookup |
由汇编源码可知,最终跳转到 bl _lookUpImpOrForward
中
验证
上述汇编的过程,可以通过 汇编调式来验证
- 在
main
中,例如[person sayNB]
对象方法调用处加一个断点,并且开启汇编调试Debug -> Debug workflow -> 勾选 Always show Disassembly
,运行程序
- 汇编中
objc_msgSend
加一个断点,执行断住,按住control + stepinto
,进入objc_msgSend
的汇编
- 在
_objc_msgSend_uncached
加一个断点,执行断住,按住control + stepinto
,进入汇编
从上面可以看出最后走到的就是 lookUpImpOrForward
,此时并不是汇编实现
1 | 注: |
MethodTableLookup(即查询方法列表)慢速查找 C/C++ 部分
- 根据汇编部分的提示全局搜索
lookUpImpOrForward
,最后在objc-runtime-new.mm
文件中找到了源码实现,这是一个c实现的函数
1 | IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior) |
其整体的慢速查找流程如下图:
主要有以下几步:
【第一步】
cache
缓存中进行查找,即快速查找
,找到则直接返回imp
,反之,则进入【第二步】【第二步】判断
cls
是否是
已知类
,如果不是,则报错
类是否
实现
,如果没有,则需要先实现,确定其父类链,此时实例化的目的是为了确定父类链、ro、以及rw等,方法后续数据的读取以及查找的循环是否
初始化
,如果没有,则初始化
【第三步】
for
循环,按照类继承链或者元类继承链
的顺序查找当前的
cls
的方法列表中使用二分查找算法
查找方法,如果找到,则进入cache写入流程
(在OC底层原理13:cache_t底层原理分析 文章中已经详述过),并返回imp
,如果 没有找到,则返回nil
当前cls
被赋值为父类
,如果父类等于nil
,则imp = 消息转发
,并终止递归,进入【第四步】如果
父类链
中存在循环,则报错,终止循环
父类缓存
中查找方法如果
未找到
,则直接返回nil
,继续循环查找
如果
找到
,则直接返回imp
,执行cache写入流程
【第四步】判断
是否执行过
动态方法解析如果
没有
,执行动态方法解析
如果
执行过
一次动态方法解析,则走到消息转发流程
以上就是方法的
慢速查找流程
,下面在分别详细解释二分查找原理
,以及父类缓存查找
详细步骤
getMethodNoSuper_nolock 方法:二分查找方法流程
查找方法列表流程:
其 二分查找
核心的源码如下:
1 | ALWAYS_INLINE static method_t * |
算法原理
简述:从第一次查找开始,每次都取 中间位置
,与想查找的 key的value的值
作比较,如果 相等
,则需要 排除分类方法
,然后将查询到的位置的方法实现返回,如果 不相等
,则需要 继续二分查找
,如果循环至 count=0
还是 没有找到
,则直接返回 nil
,如下图所示:
以查找 ZJPerson
类的 sayNB实例方法
为例,其二分查找过程如下:
cache_getImp方法:父类缓存查找
cache_getImp
方法是通过 汇编_cache_getImp实现
,传入 $0
是 GETIMP
,如下所示:
如果
父类缓存
中找到了方法实现,则跳转至CacheHit
即命中,则直接返回imp
如果在
父类缓存
中,没有找到方法实现
,则跳转至CheckMiss
或者JumpMiss
,通过判断$0
跳转至LGetImpMiss
,直接返回nil
总结
对于
对象方法(即实例方法)
,即在类中查找
,其慢速查找的父类链
是:类 -> 父类 -> 根类 -> nil
对于
类方法
,即在元类中查找
,其慢速查找的父类链
是:元类 -> 根元类 -> 根类 -> nil
如果
快速查找、慢速查找
也没有找到
方法实现,则尝试动态方法决议
如果
动态方法决议
仍然没有找到,则进行消息转发
常见方法未实现报错源码
如果在 快速查找、慢速查找、方法解析流程中
,均没有找到方法实现,则使用消息转发,其流程如下:
消息转发的实现
- 其中
_objc_msgForward_impcache
是汇编实现,会跳转至__objc_msgForward
,其核心是__objc_forward_handler
1 | STATIC_ENTRY __objc_msgForward_impcache |
- 汇编实现中查找
__objc_forward_handler
,并没有找到,在源码中去掉一个下划线进行全局搜索_objc_forward_handler
,有如下实现,本质是调用的objc_defaultForwardHandler
方法
1 | // Default forward handler halts the process. |
- 看着
objc_defaultForwardHandler
有没有很眼熟,这就是我们日常开发中最常见的错误:没有实现函数,运行程序,崩溃时的报错提示
- Post title:OC底层原理14-2:objc_msgSend方法列表查找(快速查找)汇编分析
- Post author:张建
- Create time:2020-10-05 10:12:37
- Post link:https://redefine.ohevan.com/2020/10/05/OC底层原理/OC底层原理14-2:objc_msgSend方法列表查找(慢速查找)汇编分析/
- Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.