You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{// cache 中没有找到方法,开始走 lookUpImpOrForward 查找流程// 汇编有调 --> objc_msgForward --> 找不到方法报错信息的处理const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
Class curClass;
runtimeLock.assertUnlocked();
// Optimistic cache lookupif (fastpath(behavior & LOOKUP_CACHE)) {
// 找缓存 - 是为了出现在此过程中方法又被人调用加进缓存了,有缓存了就不必继续慢速找了
imp = cache_getImp(cls, sel);
if (imp) goto done_nolock;// 找到了,去 done_nolock
}
// runtimeLock is held during isRealized and isInitialized checking// to prevent races against concurrent realization.// runtimeLock is held during method search to make// method-lookup + cache-fill atomic with respect to method addition.// Otherwise, a category could be added but ignored indefinitely because// the cache was re-filled with the old value after the cache flush on// behalf of the category.// 注释翻译不如英文准确不翻了
runtimeLock.lock();
// We don't want people to be able to craft a binary blob that looks like// a class but really isn't one and do a CFI attack.//// To make these harder we want to make sure this is a class that was// either built into the binary or legitimately registered through// objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair.//// TODO: this check is quite costly during process startup.checkIsKnownClass(cls);
if (slowpath(!cls->isRealized())) {// cls 是否已实现,否则去将类信息进行处理 类元类方法全部要处理好的 --> 为了后面的方法查找
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
// runtimeLock may have been dropped but is now locked again
}
// initialize 初始化if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
// runtimeLock may have been dropped but is now locked again// If sel == initialize, class_initialize will send +initialize and// then the messenger will send +initialize again after this// procedure finishes. Of course, if this is not being called// from the messenger then it won't happen. 2778172
}
runtimeLock.assertLocked();
curClass = cls;
// The code used to lookpu the class's cache again right after// we take the lock but for the vast majority of the cases// evidence shows this is a miss most of the time, hence a time loss.//// The only codepath calling into this without having performed some// kind of cache lookup is class_getInstanceMethod().for (unsigned attempts = unreasonableClassCount();;) {// 死循环,没有出口条件,跳出逻辑在循环内部// curClass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);// 查找方法if (meth) {// 找着了
imp = meth->imp;
goto done;
}
// 自己没找着// curClass = superClass// 是nil 则直接 没找着方法把nil的forward_imp赋给imp,并跳出循环if (slowpath((curClass = curClass->superclass) == nil)) {
// No implementation found, and method resolver didn't help.// Use forwarding.
imp = forward_imp;
break;
}
// superclass 不是 nil 继续向下走// Halt if there is a cycle in the superclass chain.// 如果超类链中存在循环,则停止if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");// 类列表的内存污染了
}
// Superclass cache.// 找父类的缓存/** CacheLookup GETIMP, _cache_getImp*/
imp = cache_getImp(curClass, sel);
/* STATIC_ENTRY _cache_getImp GetClassFromIsa_p16 p0 CacheLookup GETIMP, _cache_getImp // GETIMP,cache查找的参数是GETIMP,checkMiss LGetImpMiss:// cache 中没找到直接返回0 mov p0, #0 ret END_ENTRY _cache_getImp*/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)) {// 父类中找到了 goto done --> 对此方法进行缓存// Found the method in a superclass. Cache it in this class.goto done;
}
}
// No implementation found. Try method resolver once.// 上面找完了没找着方法 动态方法解析 resolver 一次 --> 此方法只会走一次onceif (slowpath(behavior & LOOKUP_RESOLVER)) {/* &:3 & 2 = 0011 & 0010 = 0010 第二次(方法动态处理中会再回来查一遍)再来条件就为false了: 0001 & 0010 = 0000 */
behavior ^= LOOKUP_RESOLVER;// 异或操作 behavior = 0011^0010 = 0001returnresolveMethod_locked(inst, sel, cls, behavior);
}
done:
// 查找到了对应的 Method,那么就填充到缓存log_and_fill_cache(cls, imp, sel, inst, curClass);
runtimeLock.unlock();
done_nolock:
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp;
}
objc_msgSend 慢速流程
经过上一篇
objc_msgSend
快速流程分析,通过汇编查询cache
,如果 缓存命中 就直接进行发送消息。但是如果没有命中缓存接下来就需要走慢速流程了,也就是来到上一篇结尾处所说的lookUpImpOrForward
函数里面,接下来本文将对慢速流程进行探索。在快速查找流程中,如果没有找到方法实现,最终会来到
_objc_msgSend_uncached
函数中。__objc_msgSend_uncached --> MethodTableLookup --> _lookUpImpOrForward
通过源码可以看到缓存中找不到会进入 _lookUpImpOrForward 的查找过程,汇编中搜索并未找到此方法:
1、全局搜索‘lookUpImpOrForward’,可以发现此方法是在objc-runtime-new.mm文件中C++实现的;
2、同样也可以通过 show 反汇编方式找到:Debug --> Debug Workflow --> Always show disassembly 勾上
lookUpImpOrForward
函数,所以从这里我们也可以看出,他在汇编快速查找流程没有找到方法实现的时候,会来到慢速查找流程lookUpImpOrForward
处。慢速查找方法流程分析
因为
lookUpImpOrForward
函数是支持多线程的,所以内部有很多锁操作,然后通过runtimeLock
控制读写锁。其内部有很多逻辑代码。通过类对象的
isRealized
函数,判断当前类是是否被实现,如果没有被实现,则通过realizeClassMaybeSwiftAndLeaveLocked
函数实现该类。在realizeClassMaybeSwiftAndLeaveLocked
函数中,会设置rw
、ro
、supercls
、metacls
等一些信息。lookUpImpOrForward
分析getMethodNoSuper_nolock
二分查找法的分析在方法不是第一次调用时,可以通过
cache_getImp
函数查找到缓存的IMP
。但如果是第一次调用,就查找不到缓存的IMP
,那么就会进入到getMethodNoSuper_nolock
函数中执行。下面是getMethodNoSuper_nolock
函数的实现代码。search_method_list
分析当调用一个对象的方法时,查找对象的方法,本质上就是遍历对象
isa
所指向类的方法列表,并用调用方法的SEL
和遍历的method_t
结构体的name
字段做对比,如果相等则将IMP
函数指针返回。findMethodInSortedMethodList
分析二分查找关键点和注意点:
fixupMethodList
中使用std::stable_sort
进行文档排序,确保分类的method
在前。SEL
相同的method
之后,会继续向前查找是否还有SEL
相同的method
,找到之后,那个才是最终要找的method
。这样就确保了分类的method
被优先调用。findMethodInSortedMethodList
执行逻辑count: 假设初始值为方法列表的个数为 48
如果 count != 0; 循环条件每次右移一位,也就是说除以 2;
第一次进入从一半 24 开始找起,如果 keyValue > probeValue 那么在右边,否则在左边;
第二次是从 12 开始找起,也不满足 keyValue > probeValue 的条件;
第三次从 6 开始找起,满足条件 keyValue > probeValue,将初始值移动到当前 6 的后一位,也就是从 7 开始查找,然后 count--,可以看到当前 count = 5 ,然后在对 > 6 且 < 12 进行查找,也就是 7 - 11 ,count >> 1 为 2, 7+2 = 9,刚好是 7 - 11 的中心。
这就是 二分查找法,但是前提必须是有序数组。
找不到实现方法,
Xcode
崩溃如果没有实现 动态方法决议和消息转发 就进入
_objc_msgForward_impcache
汇编STATIC_ENTRY __objc_msgForward_impcache // No stret specialization. b __objc_msgForward END_ENTRY __objc_msgForward_impcache ENTRY __objc_msgForward
上述代码发现调用了
_objc_forward_handler
函数,继续搜索。_objc_forward_handler
函数看到这里应该都明白了,这就是经常在
Xcode
控制台看到的找不到方法的崩溃信息。所以也证明了一件事,在底层是 没有对象方法和类方法之分 的。都是函数而已,实例方法是类的实例方法,类方法也是元类的实例方法。慢速查找流程图
总结
消息调用总结
消息的查找有快速流程通过
objc_msgSend
通过cache
查找、慢速流程lookUpImpOrForward
进行查找。从快速查找流程进入慢速查找流程一开始是不会进行
cache
查找的,而是直接从方法列表中进行查找。从方法的缓存列表中查找,通过
cache_getImp
函数进行查找,如果找打缓存则直接返回IMP
。首先会查找当前类的
method list
,查找是否有对应的SEL
,如果有则获取到Method
对象,并从Method
对象中获取IMP
,并返回IMP
(这一步查找的结果是Method
对象)。如果在当前类没有找到
SEL
,则进行死循环去父类的缓存列表和方法列表中查找。如果在类的继承体系中,一直都没有查找到对应的
SEL
,则进去动态方法决议。可以在+ resolveInstanceMethod
和+ resolveClassMethod
两个方法中动态添加实现。如果动态方法决议阶段没有做出任何响应,则进入动态消息转发阶段。此时可以在动态消息转发阶段做一下处理,如果还不进行处理,就会引发
Crash
。总体可以被分为以下三个部分
objc_msgSend
函数后,内部会做一些处理逻辑。IMP
的过程,会涉及到缓存列表和方法列表等等信息。The text was updated successfully, but these errors were encountered: