一、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
消息名称: personTest
objc_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、消息机制流程图