当objc_msgSend在缓存cache中找不到方法时,即源码中执行CheckMiss
和JumpMiss
,就会进行慢速查找。 git
CheckMiss 为loop循环中,
bucket->sel
未命中时调用的方法。JumpMiss为结束递归后,仍旧查找到了第一个bucket时,进行调用的方法。缓存
CheckMiss
和JumpMiss
源码中,都会因NORMAL
而进行__objc_msgSend_uncached
的操做。安全
查找__objc_msgSend_uncached
实现:markdown
继续查找MethodTableLookup
:多线程
当查询到_lookUpImpOrForward
时,就继续搜索不到了。是由于从底层汇编往上层C/C++的代码进行调用实现,即进行慢速查找过程。app
快速查找是在缓存中进行方法的查找;慢速查找是从类信息data()中,及其父类链中进行方法的查找。ide
全局搜一下lookUpImpOrForward
:函数
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{ const IMP forward_imp = (IMP)_objc_msgForward_impcache; IMP imp = nil; Class curClass; runtimeLock.assertUnlocked(); // Optimistic cache lookup if (fastpath(behavior & LOOKUP_CACHE)) { imp = cache_getImp(cls, sel); if (imp) goto 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 = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock); // runtimeLock may have been dropped but is now locked again } 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; } if (slowpath((curClass = curClass->superclass) == 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: 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; } 复制代码
1.
if (fastpath(behavior & LOOKUP_CACHE)) {
imp = cache_getImp(cls, sel);
if (imp) goto done_nolock;
}
复制代码
该判断,是为了防止在多线程操做时,恰好调用到lookUpImpOrForward
时,缓存中insert
了要查找的方法,那么能够直接从缓存获取imp,并goto done_nolock
,毕竟慢速查找是个耗时的过程,苹果尽可能不想太过耗时。oop
2.
runtimeLock.lock();
复制代码
加锁,从注释也能够看到,是为了保证操做时的线程安全问题。post
3.
checkIsKnownClass(cls);
复制代码
确保传入的类是合法注册的类,是否已经加载过。
4.
if (slowpath(!cls->isRealized())) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
}
复制代码
若是当前类没有实现,若是没有,则要实现并确认其父类链和方法属性等。
而在realizeClassWithoutSwift
方法中,会确认当前类的父类链
...
supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
...
cls->superclass = supercls;
cls->initClassIsa(metacls);
...
// Connect this class to its superclass's subclass lists
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
复制代码
而且会调用到methodizeClass
,关联类信息中的数据。
5.
if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
}
复制代码
内部会有一个递归函数,判断类及其父类是否实现isInitialized
,没有则会先进行类的初始化。
以上前几段代码,都是慢速查找的准备工做,此后的for死循环
是关键部分。
for (unsigned attempts = unreasonableClassCount();;)
这个for循环
没有判断条件等,它是个死循环。
1.
Method meth = getMethodNoSuper_nolock(curClass, sel);
复制代码
经过getMethodNoSuper_nolock
-> search_method_list_inline
-> findMethodInSortedMethodList
2.
if (meth) {
imp = meth->imp;
goto done;
}
...
done:
log_and_fill_cache(cls, imp, sel, inst, curClass);
runtimeLock.unlock();
复制代码
当二分查找到方法时,会调用到log_and_fill_cache
。
log_and_fill_cache
会调用到cache_fill
缓存填充,而在cache_fill
中,会调用到cache->insert
方法。
在类结构cache中详述了方法插入到缓存的过程。
3
if (slowpath((curClass = curClass->superclass) == nil)) {
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = forward_imp;
break;
}
复制代码
当二分查找没有找到时,此时curClass会被赋值它的父类superclass
,并判断是否为nil,是的话imp被赋值forward_imp
后跳出循环,不然继续向下。
往上看forward_imp
全局搜_objc_msgForward_impcache
,在汇编代码objc-msg-arm64.s
中 再查找到C++代码
objc-runtime.mm
中 看到了常见的
unrecognized selector sent to instance
方法未找到。
那么该段代码的含义就是,当根据类及其父类直到根类,都没有找到方法时,objc_msgSend
消息转发到了objc_defaultForwardHandler
。
4. 重点中的重点
// Superclass cache.
imp = cache_getImp(curClass, sel);
复制代码
由第三步,此时的curClass
是被赋值了它的父类,而且父类不为nil
。cache_getImp
就会从父类的缓存中进行查找。cache中查找,一样咱们须要到汇编中搜索其实现代码。
和快速查找相似,一样会调用到CacheLookup
,不过传递的参数为GETIMP
而不是快速查找时的NORMAL
。CacheLoopup
的过程再也不赘述,重点看当在缓存中也找不到时,CheckMiss
和JumpMiss
都会跳转到LGetImpMiss
,其中只有返回了空。
汇编cache_getImp
返回空没有从父类的缓存中找到方法,则会继续for死循环
,此时的curClass
变成了最初类的父类,会再次调用到Method meth = getMethodNoSuper_nolock(curClass, sel);
,从父类的类信息中进行慢速查找,直到找到跳出循环,或者找到根类的父类nil
,进行消息转发到unrecognized selector sent to instance
。
流程图总结: