1. imp快速查找流程回顾
上一篇文章我们已经详细探讨了imp
的快速查找流程,但是还未验证所探讨出来的imp
快速查找流程的正确性,因此,现在就创建一个APP
工程,运行到真机上,查看一下汇编流程。
- 创建工程
- 创建一个
Person
类,其代码如下所示:
//Person.h文件
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
- (void)saySomething:(NSString *)worlds;
- (void)sayHi;
- (void)sayHi2;
@end
NS_ASSUME_NONNULL_END
//Person.m文件
#import "Person.h"
@implementation Person
- (void)saySomething:(NSString *)worlds {
NSLog(@"%@ --- %s", worlds, __FUNCTION__);
}
- (void)sayHi {
NSLog(@"%s", __FUNCTION__);
}
- (void)sayHi2 {
NSLog(@"%s", __FUNCTION__);
}
@end
复制代码
- 在
ViewController.m
文件的ViewDidLoad
方法中创建Person
类对象并调用其这两个实例方法,在调用sayHi2
方法之前打上断点,如下图所示:
- 连接真机,编译运行程序,执行到断点的时候,显示汇编代码,如下图所示:
- 单步运行程序
- 继续单步执行,进入到
objc_msgSend
汇编部分,输出x0
寄存器的值以及[Person class]
的值,看是否一致,如下图所示:
- 汇编分析
2. imp慢速查找流程探究
2.1 从汇编回到C++代码
经过对objc_msgSend
汇编代码的静态探究以及动态验证,我们已经对方法的快速查找流程有了相当深入的了解,紧接着,我们就来探究一下当快速流程无法查找到imp
,底层又是如何处理的。
首先,紧接着快速查找流程的探索,我们在源码objc-msg-arm64.s
文件中搜索__objc_msgSend_uncached
关键字,查找到的汇编代码如下:
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band p15 is the class to search
MethodTableLookup
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached
复制代码
发现其中调用了MethodTableLookup
这个宏,搜索这个关键字,查找到的汇编代码如下:
.macro MethodTableLookup
SAVE_REGS MSGSEND
// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
// receiver and selector already in x0 and x1
mov x2, x16
mov x3, #3
bl _lookUpImpOrForward
// IMP in x0
mov x17, x0
RESTORE_REGS MSGSEND
.endmacro
复制代码
在这个宏定义中,又调用了SAVE_REGS
这个宏,这个宏是为了存储消息发送中产生的一些环境变量,之后又执行了汇编代码bl _lookUpImpOrForward
,bl
是跳转指令,一般是跳转到某个函数继续执行代码,因此_lookUpImpOrForward
应该是某个C++
函数的符号,上面的注释中表示这个函数有四个参数,第一个是receiver
,第二个是_cmd
,第三个是cls
,第四个是3,搜索lookUpImpOrForward
这个函数,其代码如下所示:
NEVER_INLINE
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();
if (slowpath(!cls->isInitialized())) {
// The first message sent to a class is often +new or +alloc, or +self
// which goes through objc_opt_* or various optimized entry points.
//
// However, the class isn't realized/initialized yet at this point,
// and the optimized entry points fall down through objc_msgSend,
// which ends up here.
//
// We really want to avoid caching these, as it can cause IMP caches
// to be made with a single entry forever.
//
// Note that this check is racy as several threads might try to
// message a given class for the first time at the same time,
// in which case we might cache anyway.
behavior |= LOOKUP_NOCACHE;
}
// 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.
checkIsKnownClass(cls);
cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
// runtimeLock may have been dropped but is now locked again
runtimeLock.assertLocked();
curClass = cls;
// The code used to lookup 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();;) {
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 {
// curClass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp(false);
goto done;
}
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
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;
}
复制代码
在这个方法中,先是一个if
语句,判断这个cls有没有初始化,如果未初始化,behavior
(初始值为3
) |=
LOOKUP_NOCACHE
(8
),然后再调用checkIsKnownClass
函数,这个函数具体代码如下:
ALWAYS_INLINE
static void
checkIsKnownClass(Class cls)
{
if (slowpath(!isKnownClass(cls))) {
_objc_fatal("Attempt to use unknown class %p.", cls);
}
}
复制代码
然后调用了isKnownClass
函数,这个函数代码如下:
static bool
isKnownClass(Class cls)
{
if (fastpath(objc::dataSegmentsRanges.contains(cls->data()->witness, (uintptr_t)cls))) {
return true;
}
auto &set = objc::allocatedClasses.get();
return set.find(cls) != set.end() || dataSegmentsContain(cls);
}
复制代码
这个函数中,初始化了一个allocatedClasses
表(表里面有所有已分配的Class
),然后遍历这个表,如果查找到了传入的cls
或者数据段也包含这个cls
,就返回true
。实际上checkIsKnownClass
函数是为了防止其他人制作一个类似但实际上不是类的class
,然后做一个CFI
进行攻击,为了使这些操作变得更难,就只能确保这是一个类,要么内置到二进制中,要么通过合法注册。
2.2 递归实现初始化Class、Class的超类以及元类
检查类之后,又调用了realizeAndInitializeIfNeeded_locked
函数对cls
进行赋值,这个函数具体代码如下:
/***********************************************************************
* realizeAndInitializeIfNeeded_locked
* Realize the given class if not already realized, and initialize it if
* not already initialized.
* inst is an instance of cls or a subclass, or nil if none is known.
* cls is the class to initialize and realize.
* initializer is true to initialize the class, false to skip initialization.
**********************************************************************/
static Class
realizeAndInitializeIfNeeded_locked(id inst, Class cls, bool initialize)
{
runtimeLock.assertLocked();
if (slowpath(!cls->isRealized())) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
// runtimeLock may have been dropped but is now locked again
}
if (slowpath(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
}
return cls;
}
复制代码
这个函数主要是用来实现并且初始化类的,首先先来看看realizeClassMaybeSwiftAndLeaveLocked
函数的实现,如下所示:
static Class
realizeClassMaybeSwiftAndLeaveLocked(Class cls, mutex_t& lock)
{
return realizeClassMaybeSwiftMaybeRelock(cls, lock, true);
}
复制代码
realizeClassMaybeSwiftMaybeRelock
函数的实现如下:
/***********************************************************************
* realizeClassMaybeSwift (MaybeRelock / AndUnlock / AndLeaveLocked)
* Realize a class that might be a Swift class.
* Returns the real class structure for the class.
* Locking:
* runtimeLock must be held on entry
* runtimeLock may be dropped during execution
* ...AndUnlock function leaves runtimeLock unlocked on exit
* ...AndLeaveLocked re-acquires runtimeLock if it was dropped
* This complication avoids repeated lock transitions in some cases.
**********************************************************************/
static Class
realizeClassMaybeSwiftMaybeRelock(Class cls, mutex_t& lock, bool leaveLocked)
{
lock.assertLocked();
if (!cls->isSwiftStable_ButAllowLegacyForNow()) {
// Non-Swift class. Realize it now with the lock still held.
// fixme wrong in the future for objc subclasses of swift classes
realizeClassWithoutSwift(cls, nil);
if (!leaveLocked) lock.unlock();
} else {
// Swift class. We need to drop locks and call the Swift
// runtime to initialize it.
lock.unlock();
cls = realizeSwiftClass(cls);
ASSERT(cls->isRealized()); // callback must have provoked realization
if (leaveLocked) lock.lock();
}
return cls;
}
复制代码
这个函数中会判断是否是一个Swift Class
,如果不是Swift Class
,就会调用realizeClassWithoutSwift
函数,其代码如下所示:
/***********************************************************************
* realizeClassWithoutSwift
* Performs first-time initialization on class cls,
* including allocating its read-write data.
* Does not perform any Swift-side initialization.
* Returns the real class structure for the class.
* Locking: runtimeLock must be write-locked by the caller
**********************************************************************/
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
runtimeLock.assertLocked();
class_rw_t *rw;
Class supercls;
Class metacls;
if (!cls) return nil;
if (cls->isRealized()) {
validateAlreadyRealizedClass(cls);
return cls;
}
ASSERT(cls == remapClass(cls));
// fixme verify class is not in an un-dlopened part of the shared cache?
auto ro = (const class_ro_t *)cls->data();
auto isMeta = ro->flags & RO_META;
if (ro->flags & RO_FUTURE) {
// This was a future class. rw data is already allocated.
rw = cls->data();
ro = cls->data()->ro();
ASSERT(!isMeta);
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// Normal class. Allocate writeable class data.
rw = objc::zalloc<class_rw_t>();
rw->set_ro(ro);
rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
cls->setData(rw);
}
cls->cache.initializeToEmptyOrPreoptimizedInDisguise();
#if FAST_CACHE_META
if (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif
// Choose an index for this class.
// Sets cls->instancesRequireRawIsa if indexes no more indexes are available
cls->chooseClassArrayIndex();
if (PrintConnecting) {
_objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
cls->nameForLogging(), isMeta ? " (meta)" : "",
(void*)cls, ro, cls->classArrayIndex(),
cls->isSwiftStable() ? "(swift)" : "",
cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
}
// Realize superclass and metaclass, if they aren't already.
// This needs to be done after RW_REALIZED is set above, for root classes.
// This needs to be done after class index is chosen, for root metaclasses.
// This assumes that none of those classes have Swift contents,
// or that Swift's initializers have already been called.
// fixme that assumption will be wrong if we add support
// for ObjC subclasses of Swift classes.
supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
#if SUPPORT_NONPOINTER_ISA
if (isMeta) {
// Metaclasses do not need any features from non pointer ISA
// This allows for a faspath for classes in objc_retain/objc_release.
cls->setInstancesRequireRawIsa();
} else {
// Disable non-pointer isa for some classes and/or platforms.
// Set instancesRequireRawIsa.
bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
bool rawIsaIsInherited = false;
static bool hackedDispatch = false;
if (DisableNonpointerIsa) {
// Non-pointer isa disabled by environment or app SDK version
instancesRequireRawIsa = true;
}
else if (!hackedDispatch && 0 == strcmp(ro->getName(), "OS_object"))
{
// hack for libdispatch et al - isa also acts as vtable pointer
hackedDispatch = true;
instancesRequireRawIsa = true;
}
else if (supercls && supercls->getSuperclass() &&
supercls->instancesRequireRawIsa())
{
// This is also propagated by addSubclass()
// but nonpointer isa setup needs it earlier.
// Special case: instancesRequireRawIsa does not propagate
// from root class to root metaclass
instancesRequireRawIsa = true;
rawIsaIsInherited = true;
}
if (instancesRequireRawIsa) {
cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
}
}
// SUPPORT_NONPOINTER_ISA
#endif
// Update superclass and metaclass in case of remapping
cls->setSuperclass(supercls);
cls->initClassIsa(metacls);
// Reconcile instance variable offsets / layout.
// This may reallocate class_ro_t, updating our ro variable.
if (supercls && !isMeta) reconcileInstanceVariables(cls, supercls, ro);
// Set fastInstanceSize if it wasn't set already.
cls->setInstanceSize(ro->instanceSize);
// Copy some flags from ro to rw
if (ro->flags & RO_HAS_CXX_STRUCTORS) {
cls->setHasCxxDtor();
if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
cls->setHasCxxCtor();
}
}
// Propagate the associated objects forbidden flag from ro or from
// the superclass.
if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
(supercls && supercls->forbidsAssociatedObjects()))
{
rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
}
// Connect this class to its superclass's subclass lists
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
// Attach categories
methodizeClass(cls, previously);
return cls;
}
复制代码
这个函数是实现Class
(主要是为了给Class
中的class_rw_t
以及class_ro_t
结构体中的数据赋值,就是Class
的实例变量列表、方法列表),并且会递归调用这个函数实现其父类以及元类,并给Class
的父类以及Metaclass
赋值,系统为什么会这么做呢,因为慢速查找流程实际上是遍历Class
中的Methods_list
中进行查找,但如果在Class
中查找不到就会结束查找吗?肯定不会的,因为Class
的实例对象调用的也可能是父类中定义的方法,所以也要遍历父类中的Methods_list
查找方法,而类方法也是如此。
实现完Class
之后,就会进行Class
的初始化,初始化调用的函数是initializeAndLeaveLocked
,具体代码如下:
// Locking: caller must hold runtimeLock; this may drop and re-acquire it
static Class initializeAndLeaveLocked(Class cls, id obj, mutex_t& lock)
{
return initializeAndMaybeRelock(cls, obj, lock, true);
}
复制代码
而initializeAndMaybeRelock
函数中代码如下所示:
/***********************************************************************
* class_initialize. Send the '+initialize' message on demand to any
* uninitialized class. Force initialization of superclasses first.
* inst is an instance of cls, or nil. Non-nil is better for performance.
* Returns the class pointer. If the class was unrealized then
* it may be reallocated.
* Locking:
* runtimeLock must be held by the caller
* This function may drop the lock.
* On exit the lock is re-acquired or dropped as requested by leaveLocked.
**********************************************************************/
static Class initializeAndMaybeRelock(Class cls, id inst,
mutex_t& lock, bool leaveLocked)
{
lock.assertLocked();
ASSERT(cls->isRealized());
if (cls->isInitialized()) {
if (!leaveLocked) lock.unlock();
return cls;
}
// Find the non-meta class for cls, if it is not already one.
// The +initialize message is sent to the non-meta class object.
Class nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
// Realize the non-meta class if necessary.
if (nonmeta->isRealized()) {
// nonmeta is cls, which was already realized
// OR nonmeta is distinct, but is already realized
// - nothing else to do
lock.unlock();
} else {
nonmeta = realizeClassMaybeSwiftAndUnlock(nonmeta, lock);
// runtimeLock is now unlocked
// fixme Swift can't relocate the class today,
// but someday it will:
cls = object_getClass(nonmeta);
}
// runtimeLock is now unlocked, for +initialize dispatch
ASSERT(nonmeta->isRealized());
initializeNonMetaClass(nonmeta);
if (leaveLocked) runtimeLock.lock();
return cls;
}
复制代码
这个函数的作用就是按需发送’+initialize
‘消息给消息给任意未初始化的类。首先强制初始化超类,参数inst
是cls
的一个实例,也可以为nil
,返回一个类指针,如果这个类没有被实现可以重新分配。此函数可能会取消锁(runtimeLock
必须由调用者持有),在退出时,按照leaveLocked
的请求重新获取或删除锁。在函数体中,首先判断这个cls
是否已经初始化,已初始化就直接返回这个cls
,如果没有初始化就找到cls
的非元类(如果cls
是一个元类),获取到nonmeta
之后,还会再次判断这个非元类是否已被实现,如果未实现就调用realizeClassMaybeSwiftAndUnlock
函数进行实现,之后调用initializeNonMetaClass
函数进行nonmeta
的初始化,这个函数代码如下所示:
/***********************************************************************
* class_initialize. Send the '+initialize' message on demand to any
* uninitialized class. Force initialization of superclasses first.
**********************************************************************/
void initializeNonMetaClass(Class cls)
{
ASSERT(!cls->isMetaClass());
Class supercls;
bool reallyInitialize = NO;
// Make sure super is done initializing BEFORE beginning to initialize cls.
// See note about deadlock above.
supercls = cls->getSuperclass();
if (supercls && !supercls->isInitialized()) {
initializeNonMetaClass(supercls);
}
// Try to atomically set CLS_INITIALIZING.
SmallVector<_objc_willInitializeClassCallback, 1> localWillInitializeFuncs;
{
monitor_locker_t lock(classInitLock);
if (!cls->isInitialized() && !cls->isInitializing()) {
cls->setInitializing();
reallyInitialize = YES;
// Grab a copy of the will-initialize funcs with the lock held.
localWillInitializeFuncs.initFrom(willInitializeFuncs);
}
}
if (reallyInitialize) {
// We successfully set the CLS_INITIALIZING bit. Initialize the class.
// Record that we're initializing this class so we can message it.
_setThisThreadIsInitializingClass(cls);
if (MultithreadedForkChild) {
// LOL JK we don't really call +initialize methods after fork().
performForkChildInitialize(cls, supercls);
return;
}
for (auto callback : localWillInitializeFuncs)
callback.f(callback.context, cls);
// Send the +initialize message.
// Note that +initialize is sent to the superclass (again) if
// this class doesn't implement +initialize. 2157218
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: calling +[%s initialize]",
objc_thread_self(), cls->nameForLogging());
}
// Exceptions: A +initialize call that throws an exception
// is deemed to be a complete and successful +initialize.
//
// Only __OBJC2__ adds these handlers. !__OBJC2__ has a
// bootstrapping problem of this versus CF's call to
// objc_exception_set_functions().
#if __OBJC2__
@try
#endif
{
callInitialize(cls);
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
objc_thread_self(), cls->nameForLogging());
}
}
#if __OBJC2__
@catch (...) {
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: +[%s initialize] "
"threw an exception",
objc_thread_self(), cls->nameForLogging());
}
@throw;
}
@finally
#endif
{
// Done initializing.
lockAndFinishInitializing(cls, supercls);
}
return;
}
else if (cls->isInitializing()) {
// We couldn't set INITIALIZING because INITIALIZING was already set.
// If this thread set it earlier, continue normally.
// If some other thread set it, block until initialize is done.
// It's ok if INITIALIZING changes to INITIALIZED while we're here,
// because we safely check for INITIALIZED inside the lock
// before blocking.
if (_thisThreadIsInitializingClass(cls)) {
return;
} else if (!MultithreadedForkChild) {
waitForInitializeToComplete(cls);
return;
} else {
// We're on the child side of fork(), facing a class that
// was initializing by some other thread when fork() was called.
_setThisThreadIsInitializingClass(cls);
performForkChildInitialize(cls, supercls);
}
}
else if (cls->isInitialized()) {
// Set CLS_INITIALIZING failed because someone else already
// initialized the class. Continue normally.
// NOTE this check must come AFTER the ISINITIALIZING case.
// Otherwise: Another thread is initializing this class. ISINITIALIZED
// is false. Skip this clause. Then the other thread finishes
// initialization and sets INITIALIZING=no and INITIALIZED=yes.
// Skip the ISINITIALIZING clause. Die horribly.
return;
}
else {
// We shouldn't be here.
_objc_fatal("thread-safe class init in objc runtime is buggy!");
}
}
复制代码
在这个函数中,首先会进行一次断言,cls
是非元类才能继续执行代码,然后会递归调用本函数,进行超类的初始化,然后再尝试自动设置CLS_INITIALIZING
,如果类未初始化完成并且类不在初始化中,就会调用callInitialize函数进行初始化
,这个函数的代码如下所示:
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
asm("");
}
复制代码
这个初始化方法实际上是调用了objc_msgSend
函数发送了一个initialize
消息给cls
,看到这里,我们大概就清楚的了解了Class
初始化的流程了。
2.3 二分查找method
在递归实现以及初始化Class
之后,就会继续执行lookUpImpOrForward
函数中for
循环的代码,但是这个for
循环很特别,没有条件语句,也没有自增自减语句,因此,如果没有跳出循环的语句(break, goto
),这就是个死循环,for
循环中首先调用了curClass->cache.isConstantOptimizedCache(/* strict */true)
函数来判断是否不断地优化缓存,这个函数在非真机环境下,返回的是false
,因此会执行getMethodNoSuper_nolock
函数查找curClass
中Methods_list
是否有sel
对应的函数实现imp
,这个函数代码如下所示:
/***********************************************************************
* getMethodNoSuper_nolock
* fixme
* Locking: runtimeLock must be read- or write-locked by the caller
**********************************************************************/
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
// fixme nil cls?
// fixme nil sel?
auto const methods = cls->data()->methods();
for (auto mlists = methods.beginLists(),
end = methods.endLists();
mlists != end;
++mlists)
{
// <rdar://problem/46904873> getMethodNoSuper_nolock is the hottest
// caller of search_method_list, inlining it turns
// getMethodNoSuper_nolock into a frame-less function and eliminates
// any store from this codepath.
method_t *m = search_method_list_inline(*mlists, sel);
if (m) return m;
}
return nil;
}
复制代码
这个函数中首先调用获取cls
的methods
函数获取方法列表,然后遍历methods
中所有的方法,但是这个methods
函数返回的值是一个二维的,因此又会调用search_method_list_inline
函数查找这个二维数组中每个一维方法数组列表,其代码如下所示:
ALWAYS_INLINE static method_t *
search_method_list_inline(const method_list_t *mlist, SEL sel)
{
int methodListIsFixedUp = mlist->isFixedUp();
int methodListHasExpectedSize = mlist->isExpectedSize();
if (fastpath(methodListIsFixedUp && methodListHasExpectedSize)) {
return findMethodInSortedMethodList(sel, mlist);
} else {
// Linear search of unsorted method list
if (auto *m = findMethodInUnsortedMethodList(sel, mlist))
return m;
}
#if DEBUG
// sanity-check negative results
if (mlist->isFixedUp()) {
for (auto& meth : *mlist) {
if (meth.name() == sel) {
_objc_fatal("linear search worked when binary search did not");
}
}
}
#endif
return nil;
}
复制代码
这个函数中主要调用了findMethodInSortedMethodList
函数查找方法,其代码如下所示:
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
if (list->isSmallList()) {
if (CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS && objc::inSharedCache((uintptr_t)list)) {
return findMethodInSortedMethodList(key, list, [](method_t &m) { return m.getSmallNameAsSEL(); });
} else {
return findMethodInSortedMethodList(key, list, [](method_t &m) { return m.getSmallNameAsSELRef(); });
}
} else {
return findMethodInSortedMethodList(key, list, [](method_t &m) { return m.big().name; });
}
}
复制代码
在这个函数中又调用了findMethodInSortedMethodList
函数,其代码如下所示:
/***********************************************************************
* search_method_list_inline
**********************************************************************/
template<class getNameFunc>
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName)
{
ASSERT(list);
auto first = list->begin();
auto base = first;
decltype(first) probe;
uintptr_t keyValue = (uintptr_t)key;
uint32_t count;
for (count = list->count; count != 0; count >>= 1) {
probe = base + (count >> 1);
uintptr_t probeValue = (uintptr_t)getName(probe);
if (keyValue == probeValue) {
// `probe` is a match.
// Rewind looking for the *first* occurrence of this value.
// This is required for correct category overrides.
while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) {
probe--;
}
return &*probe;
}
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
复制代码
这个就是二分查找算法的实现函数,假设list
是有10
个从小到大排序完毕的方法数组,我们要查找的sel
正好为其第7
个元素,那么查找流程如下图所示:
2.4 从C++又回到汇编
二分查找中如果查找不到sel
对应的imp
实现,就会返回nil
,回到lookupImpOrForward
函数的for
循环中,查找不到sel
在本cls
的Methods_list
中所对应impMethod
,meth
就为nil
,接着就会获取curClass
的超类赋值给curClass
,然后再调用cache_getImp
函数查找超类中缓存中是否存在sel
所对应的imp
实现,所有我们再来看一下cache_getImp
函数的实现,如下所示:
但是在源码中只找到了方法的声明,并没有找到实现,因此,全局搜索一下cache_getImp
,结果找到了objc-msg-arm64.s
文件中的汇编代码,如下所示:
STATIC_ENTRY _cache_getImp
GetClassFromIsa_p16 p0, 0
CacheLookup GETIMP, _cache_getImp, LGetImpMissDynamic, LGetImpMissConstant
LGetImpMissDynamic:
mov p0, #0
ret
LGetImpMissConstant:
mov p0, p2
ret
END_ENTRY _cache_getImp
复制代码
这里的汇编代码,会获取isa
中的class
,然后调用了我们所熟悉的CacheLookup
这个宏中的汇编代码,在我们之前快速查找流程流程的探究中,我们知道CacheLookup
如果能够查找到sel
所对应的imp,就会直接跳转执行这个imp,如果找不到就会执行MissLabelDynamic
,很显然,在这次cache_getImp
调用中,LGetImpMissDynamic
就是MissLabelDynamic
所代表的实际参数,执行LGetImpMissDynamic
这块汇编就会返回nil
。
2.5 慢速查找的过程中找到了方法实现
如果在Class
的Methods_list
中或父类的缓存或者父类的Methods_list
中找到了imp
的实现,就会执行goto done
跳出循环,而在done
这个代码块中,就会判断是不是通过快速查找流程找到sel
所对应imp
的,如果不是就会调用log_and_fill_cache
函数,其代码如下所示:
/***********************************************************************
* log_and_fill_cache
* Log this method call. If the logger permits it, fill the method cache.
* cls is the method whose cache should be filled.
* implementer is the class that owns the implementation in question.
**********************************************************************/
static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
if (slowpath(objcMsgLogEnabled && implementer)) {
bool cacheIt = logMessageSend(implementer->isMetaClass(),
cls->nameForLogging(),
implementer->nameForLogging(),
sel);
if (!cacheIt) return;
}
#endif
cls->cache.insert(sel, imp, receiver);
}
复制代码
通过分析代码我们发现,log_and_fill_cache
这个函数又调用了我们很熟悉的这个函数insert
函数,而inset
函数就是将查找到的imp
以及对应的sel
存储在所在class
的cache
中,以便下次调用这个sel
的imp
时,可以通过快速查找的方式马上调用sel
所对应的实现。
2.5 快速、慢速流程查找都未找到imp
在以上的慢速查找流程中,如果在curClass中methods_list
中未找到sel
所对应imp的实现,就会获取其父类,然后在其父类的cache
中查找sel
所对应的imp实现,就会查找其父类中的methods_list
是否能找到,如果找不到,就会获取其父类的父类,然后重复以上的流程,直到遍历到继承链的末尾(为nil
),如果都找不到sel所对应imp的,就会将forward_imp
赋值给imp
,然后返回,而forward_imp
就是_objc_msgForward_impcache
,关于_objc_msgForward_impcache
函数,这个函数的作用以及意义我们将在下篇文章进行探讨。
3. 慢速查找流程总结
3.1 慢速查找流程简述
- 从快速查找流程中的汇编代码
objc_msgSend_uncached
到C++
函数lookupImpOrForward
。 - 递归实现
Class
继承链中所有的superClass
以及isa
指向链中所有的mataclass
,初始化Class
继承链中所有的superClass
。 - 使用二分查找算法查找
curClass
的Methods_list
中是否有sel
所对应的imp
,如果查找成功,执行步骤4
;未查找到,执行步骤5
。 - 判断是否是通过快速查找方式获取得到的
imp
,如果是,返回imp,执行步骤10
;不是,执行步骤6
。 - 判断其父类是否为存在,不存在,执行
步骤7
,存在,curClass
赋值为所获取到的父类,执行步骤8
。 - 调用
log_and_fill_cache
函数将通过慢速查找所获取到的sel
、imp
缓存到所对应class
的成员变量cache
的buckets
中,然后返回这个imp
,执行步骤10
。 - 将
imp
赋值为forward_imp
,然后返回imp
。 - 使用
cache_getImp
判断父类cache中是否有对sel
及sel
对应imp
进行缓存,如果缓存中存在,执行步骤9
,如果缓存中不存在,执行步骤3
。 - 从缓存中获取
sel
所对应的imp
,并跳转执行。 - 跳转到
lookupImpOrForward
函数所返回的imp
执行代码。