方法的本质
在前面的文章:对象的本质中,使用Clang
探索了对象的本质。同样我们使用Clang
获取.cpp
文件查看main
中方法的实现。
//main.m中的代码
HTPerson *person = [HTPerson alloc];
[person sayNB];
[person playGame:@"王者荣耀"];
//编译成main.cpp后的代码
HTPerson *person = ((HTPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("HTPerson"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayNB"));
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)person, sel_registerName("playGame:"), (NSString *)&__NSConstantStringImpl__var_folders_pb_ltmc_s613_76jpz77vf9cvx40000gn_T_main_dad2e6_mi_3);
可以看出,方法的其实就是一个objc_msgSend
的消息,而方法的执行,其实就是查找方法的实现地址,并且执行该方法的过程。
//self是消息的接受者 SEL是方法名 在后面是一些参数
objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
对象在收到objc_msgSend
消息后,会根据isa
指向的类中查找方法的实现地址。查找流程大致如下:
- 快速查找:去过类的缓存
cacte_t
中有对应的方法实现,那么返回该方法地址。 - 慢速查找:如果没有缓存,会去查找
bits
中的methods
。如果没有继续去superclass
查找相应的方法。
方法的快速查询(获取cache中的方法)
在objc
源码中搜索objc_msgSend
会在objc-msg-arm64
文件中看到ENTRY _objc_msgSend
。这里是使用汇编进行方法的查询。大致流程如下:
- 判断
objc_msgSend
方法的第一个参数receiver
是否为空,并且获取到isa
- 进入到快速方法查询
CacheLookup
- 通过首地址平移16字节(因为在
objc_class
中,首地址距离cache正好16字节,即isa首地址 占8字节,superClass占8字节),获取cahce
- 从cache中分别取出
buckets
和mask
,并由mask根据哈希算法计算出哈希下标 - 递归查询对应的bucket中的
sel
和 message中的op
是否一致,如果一致返回,不一致进入慢查询方法(__objc_msgSend_uncached
)
方法的慢速查询
慢速查询的主要流程:
- 从汇编中__objc_msgSend_uncached的方法到底层
lookUpImpOrForward
的实现 - lookUpImpOrForward中主要做三件事
初始化
所有的父类- 死循环
二分法查找类的方法
,查找父类缓存的方法,再次二分法查询父类的方法... - 把获取到的方法imp
缓存
到cache中
初始化所有的父类
方法realizeAndInitializeIfNeeded_locked
点进去里面有实现方法。判断类是否实现,如果没有,则需要先实现,确定其父类链,此时实例化的目的是为了确定父类链、ro、以及rw等,方法后续数据的读取以及查找的循环
二分法查找类的方法
方法getMethodNoSuper_nolock
点进去。会发现
- 根据向二进制位右移动一位的方法,实现二分法查询
- 如果有相同的方法名称,会去取前面的方法名返回(分类的方法会存在同名本类方法的前面)
把获取到的方法imp缓存到cache中
方法log_and_fill_cache
缓存方法的imp
。
如果没有找到imp
,则进行动态方法决议和消息转发。在下一节会介绍。
总结
- 类方法执行的顺序是由查询顺序所决定的。(子类 - 父类 - NSObject)
- 在二分法查询时,会决定先执行分类方法。
- 找不到方法时会尝试动态方法决议
- 如果动态方法决议仍然没有找到,则进行消息转发 源码如下所示
// 缓存 找不到 - lookUpImpOrForward - 慢速 - methodlist
// 汇编 缓存 参数未知
NEVER_INLINE
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
// 创建forward_imp,并给定默认值_objc_msgForward_impcache
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
// 创建imp,用于接收通过sel查找的imp
IMP imp = nil;
// 创建要查找的类,这个类通过isa的指向关系是会一直变化的,
// 直到最终指向NSObject的父类nil为止
Class curClass;
runtimeLock.assertUnlocked();
// 发送到类的第一条消息通常是 +new 或 +alloc 或 +self
// 但是,此时该类尚未初始化,此时behavior = 3|8 = 11
// 当向将上+new等这些方法inset进缓存的时候
// 不满足behavior & LOOKUP_NOCACHE) == 0这个条件,8 & 11 = 8
// 所以上述这些方法不会加载进缓存。
//
// 如果类已经初始化了,就不会修改behavior的值了,behavior=3
// 我们自定义的方法是可以正常加载进缓存的。
if (slowpath(!cls->isInitialized())) {
behavior |= LOOKUP_NOCACHE;
}
// runtimeLock 在 isRealized 和 isInitialized 检查期间被持有
// 加锁-防止与并发实现竞争。
runtimeLock.lock();
// 检查是否是注册类
checkIsKnownClass(cls);
// 初始化跟cls实例对象在isa指向图中的每一个类(class和metaClass)
// 以便后续自己类里面找不到方法去父类里面找
// 依次向上找
// 所以在此处对所有相关的类进行了初始化
cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
// runtimeLock may have been dropped but is now locked again
runtimeLock.assertLocked();
// curClass为当前实例对象的类
curClass = cls;
// 死循环查找
// 循环查找类对象的methodList,当前类没有的话就找父类
// 父类没有就找父类的父类,一直找到NSObject类
// 如果NSObject都找不到的话最终curClass会指向nil
// 将事先准备好的forward_imp赋值给imp
// 然后结束慢速查找流程,接下来进入Runtime消息转发机制
for (unsigned attempts = unreasonableClassCount();;) {
// 第一步先找共享缓存里面有没有我们的方法
// 通常情况下我们的自定义方法不会出现在共享缓存中
if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
imp = cache_getImp(curClass, sel);
if (imp) goto done_unlock;
curClass = curClass->cache.preoptFallbackClass();
#endif
} else {
// 在当前类的方法列表里面查找,这是重点
// 查找算法是二分法
Method meth = getMethodNoSuper_nolock(curClass, sel);
//如果找到了方法
if (meth) {
imp = meth->imp(false);
//跳转到done
goto done;
}
// 如果当前类找不到,取将curClass指向superclass
// 查询父类的methodList,一直找到NSObject的父类nil为止
// 将事先准备好的forward_imp赋值给imp
// 然后结束慢速查找流程,接下来进入Runtime消息转发机制
// 结束循环遍历
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = forward_imp;
break;
}
}
// Halt if there is a cycle in the superclass chain.
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
// 父类 快速 -> 慢速 -> 父父类 快速 -> 慢速
imp = cache_getImp(curClass, sel);
if (slowpath(imp == forward_imp)) {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
//如果找到了
if (fastpath(imp)) {
//缓存
// Found the method in a superclass. Cache it in this class.
goto done;
}
}
// No implementation found. Try method resolver once.
// 没有找到方法,尝试一次方法解析
if (slowpath(behavior & LOOKUP_RESOLVER)) {
//动态方法决议的控制条件,表示流程只走一次
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
done://缓存
if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES
while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
cls = cls->cache.preoptFallbackClass();
}
#endif
//imp加入到缓存
log_and_fill_cache(cls, imp, sel, inst, curClass);
}
done_unlock:
//解锁
runtimeLock.unlock();
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp;
}
慢查询流程图如下
二分法查询实现如下