一、objc_msgSend
- 创建命令行项目, 定义
Person
类, 并添加personTest
方法
- 使用终端, 执行下面的命令, 生成
main.m
在底层的main.cpp
文件
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m复制代码
- 其中
[person personTest]
和[Person initialize]
转化为了下面的代码
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("personTest"));((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("initialize"));复制代码
objc_msgSend(person, sel_registerName("personTest"));objc_msgSend(objc_getClass("Person"), sel_registerName("initialize"));复制代码
- OC中调用方法, 会使用
objc_msgSend
函数给消息接收者
发送消息 - 所以OC调用方法的过程也被称为
消息机制
- 上面两个方法调用的消息接收者和消息名称如下
objc_msgSend(person, sel_registerName("personTest"));消息接收者: person消息名称: personTestobjc_msgSend(objc_getClass("Person"), sel_registerName("initialize"));消息接收者: objc_getClass("Person") == [Person class]消息名称: initialize复制代码
二、objc_msgSend底层源码
- 可以在官方提供的源码中找到
objc_msgSend
的底层实现, 可以看到objc_msgSend
底层是由汇编实现的
1、当消息接收者为空时, 直接返回, 结束objc_msgSend
函数的调用
- 通过源码可以看到,
objc_msgSend
执行一开始, 会先判断消息接收者
是否为空, 如果为空直接返回
void objc_msgSend(id receiver, SEL selector) { // 当 消息接收者 为空时, 直接返回, 结束函数调用 if (receiver == nil) return;}复制代码
2、当消息接收者有值时, 查看缓存
- 当消息接收者有值时, 会调用
CacheLookup
- 在
CacheLookup
, 首先会查找cache
缓存, 查询是否已经缓存了方法, 如果已经缓存会直接调用
3、如果方法没有被缓存过, 就会查询方法列表
- 当方法还没有缓存, 会从方法列表中查找, 此时调用
CheckMiss
, 传入NORMAL
- 找到
CheckMiss
中NORMAL
部分, 调用__objc_msgSend_uncached
- 接着可以看到调用了
__class_lookupMethodAndLoadCache3
函数
- 搜索
__class_lookupMethodAndLoadCache3
函数, 可以发现并没有__class_lookupMethodAndLoadCache3
的实现部分
- 这主要是因为汇编中的
__class_lookupMethodAndLoadCache3
函数, 在C中的函数名需要去掉一个下划线_
, 所以函数名应该是_class_lookupMethodAndLoadCache3
- 查找
_class_lookupMethodAndLoadCache3
, 可以发现_class_lookupMethodAndLoadCache3
函数的实现部分
- 继续进入
lookUpImpOrForward
函数, 可以看到当传入cache
为YES
时, 会查找缓存中的方法 - 此时传入
cache
的是NO
, 所以不会在调用cache_getImp
函数从缓存中查找方法
- 然而我们还能看到下面还有从缓存中查找方法的代码, 如果找到直接跳转返回
- 这主要是因为在下图过程中, 开发者有可能会动态添加一些方法到缓存中, 所以才会再次查找一次
- 当缓存中没有找到需要调用的方法时, 就会在方法列表中查找, 如果找到就会存到缓存
cache
中
- 已经知道, 方法存在
cls->rw->methods
中, 而methods
是个二位数组, 所以需要进行遍历查询, 这里先拿到一维数组调用search_method_list
函数查询
- 在一维数组中查找方法, 这里有两种情况
- 第一种: 方法列表已经排好序, 会通过
findMethodInSortedMethodList
函数查找 - 第二种: 方法列表没有排好序, 会一个一个遍历查找
findMethodInSortedMethodList
函数使用的是二分查找
的方式查询方法
- 当找到方法后, 会先将方法存储到
cache
中, 调用log_and_fill_cache
函数进行存储
log_and_fill_cache
中调用cache_fill
函数
cache_fill
函数中调用cache_fill_nolock
函数
- 在
cache_fill_nolock
函数中,可以看到方法存储到了buckets
中
- 如果在自己的类对象中没有找到需要调用的方法, 就会去查找父类中是否有该方法
- 1.查找时会一层一层遍历所有父类, 只要某个父类中找到方法, 就会结束查找
- 2.先从父类的缓存中找, 如果找到, 会先存到自己的
cache
中 - 3.如果父类的缓存中没有该方法, 就会从父类的方法列表中查找, 如果找到就会存入到自己的
cache
中, 并不会存入到父类的cache
中 - 4.如果没找到, 就会通过for循环查看父类的父类中有没有方法, 依次类推, 只要找到就会结束查询, 并存到自己的
cache
中
- 如果最后还是没找到, 就会进入下一个阶段, 动态解析阶段
4、消息机制流程图