iOS之深入解析Runtime的objc_msgSend“慢速查找”底层原理

CacheLookup 快速查找

  • objc_msgSend 通过汇编 快速查找方法缓存 ,如果能找到则调用 TailCallCachedImp 直接将方法缓存起来然后进行调用,如果查找不到就跳到 CheckMiss ,然后走 慢速查找流程 。该文主要分析 objc_msgSend 慢速查找流程。
  • objc_msgSend 的快速查找请参考之前的文章:iOS之深入解析Runtime的objc_msgSend“快速查找”底层原理

__objc_msgSend_uncached 慢速查找入口

  • 定义以下对象和方法实现:
	@interface YDWPerson : NSObject
	@property (nonatomic, copy) NSString *lgName;
	@property (nonatomic, strong) NSString *nickName;
	- (void)sayNB;
	- (void)sayMaster;
	- (void)say666;
	- (void)sayHello;
	@end
	@implementation YDWPerson
	- (void)sayNB{
    
    
    NSLog(@"%s",__func__);
	}
	- (void)sayMaster{
    
    
	    NSLog(@"%s",__func__);
	}
	- (void)say666 {
    
    
	    NSLog(@"%s",__func__);
	}
	- (void)sayHello {
    
    
	    NSLog(@"%s",__func__);
	}
	@end
  • 在main函数中调用:
	YDWPerson *person = [YDWPerson alloc];
    [person say666];
  • 在Xcode - Debug - Debug WorkFlow 中将 Always show Disamessbly 打开,跟着断点调试:

在这里插入图片描述

  • 按着control,点击红圈选择键进行单步调试发现:

在这里插入图片描述

  • 方法缓存查找不到之后会调用__objc_msgSend_uncached:

在这里插入图片描述

  • 继续:

在这里插入图片描述

  • 发现执行了 lookUpImpOrForward 方法,在源码 objc-runtime-new.mm 文件中的6095行。

__objc_msgSend_uncached 慢速查找源码分析

  • CheckMiss 源码实现如下:
	.macro CheckMiss
	    // miss if bucket->sel == 0
	.if $0 == GETIMP
	    cbz p9, LGetImpMiss
	.elseif $0 == NORMAL 
	    cbz p9, __objc_msgSend_uncached
	.elseif $0 == LOOKUP
	    cbz p9, __objc_msgLookup_uncached
	.else
	.abort oops
	.endif
	.endmacro
  • 如果当前的 bucket 等于 buckets 的第一个元素,则直接跳转至 JumpMiss ,此时的 $0 是 normal ,也是直接跳转至 __objc_msgSend_uncached 。__objc_msgSend_uncached 源码实现如下:
    STATIC_ENTRY __objc_msgSend_uncached
    UNWIND __objc_msgSend_uncached, FrameWithNoSaves

    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band p16 is the class to search
    
    MethodTableLookup
    TailCallFunctionPointer x17

    END_ENTRY __objc_msgSend_uncached
  • __objc_msgSend_uncached 中最核心的逻辑就是 MethodTableLookup(即查询方法列表) ,因为 缓存并没有命中,这里是开始去 方法列表 (methodList) 的查找流程;
.macro MethodTableLookup
    
    // push frame
    SignLR
    stp fp, lr, [sp, #-16]!
    mov fp, sp

    // save parameter registers: x0..x8, q0..q7
    sub sp, sp, #(10*8 + 8*16)
    stp q0, q1, [sp, #(0*16)]
    stp q2, q3, [sp, #(2*16)]
    stp q4, q5, [sp, #(4*16)]
    stp q6, q7, [sp, #(6*16)]
    stp x0, x1, [sp, #(8*16+0*8)]
    stp x2, x3, [sp, #(8*16+2*8)]
    stp x4, x5, [sp, #(8*16+4*8)]
    stp x6, x7, [sp, #(8*16+6*8)]
    str x8,     [sp, #(8*16+8*8)]

    // 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 registers and return
    ldp q0, q1, [sp, #(0*16)]
    ldp q2, q3, [sp, #(2*16)]
    ldp q4, q5, [sp, #(4*16)]
    ldp q6, q7, [sp, #(6*16)]
    ldp x0, x1, [sp, #(8*16+0*8)]
    ldp x2, x3, [sp, #(8*16+2*8)]
    ldp x4, x5, [sp, #(8*16+4*8)]
    ldp x6, x7, [sp, #(8*16+6*8)]
    ldr x8,     [sp, #(8*16+8*8)]

    mov sp, fp
    ldp fp, lr, [sp], #16
    AuthenticateLR

.endmacro
  • 源码分析:
    • 如果查找的对象方法那obj 就是实例对象,cls就是类对象;
    • 如果查找的类方法那obj 就是类对象,cls就是元类对象;
    • cache = NO从消息快速查找过来不需要去检查缓存;
    • initialize = YES 不避免调用 +initiallize;
    • resolver = YES 如果未找到,则进行方法解析;
  • 全局搜索,可以找到 lookUpImpOrForward 在 objc-runtime-new.mm 文件中有源码实现,这是一个 c 实现的函数:
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 (fastpath(behavior & LOOKUP_CACHE)) {
    
     
        imp = cache_getImp(cls, sel);
        if (imp) goto done_nolock;
    }

    runtimeLock.lock();

    checkIsKnownClass(cls); 

    if (slowpath(!cls->isRealized())) {
    
     
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
    }

    if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
    
     
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
    }

    runtimeLock.assertLocked();
    curClass = cls;

    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)) {
    
    
            break;
        }
        if (fastpath(imp)) {
    
    
            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;
}
  • 源码分析:
    • const IMP forward_imp = (IMP)_objc_msgForward_impcache :定义消息转发;
    • if (fastpath(behavior & LOOKUP_CACHE)) { imp = cache_getImp(cls, sel); if (imp) goto done_nolock;} :快速查找,如果找到则直接返回 imp;
    • runtimeLock.lock() :加锁保证读取的线程安全;
    • slowpath(!cls->isRealized()) :判断类是否实例化,如果没有,需要先实例化;
    • log_and_fill_cache(cls, imp, sel, inst, curClass) :存储到缓存;
    • runtimeLock.unlock() :解锁;
  • 判断缓存是否存在,存在则直接通过 cls sel 直接获取 imp ,并返回;
	if (fastpath(behavior & LOOKUP_CACHE)) {
    
    
	     imp = cache_getImp(cls, sel);
	     if (imp) goto done_nolock;
	 }
  • 根据所有已知类的列表检查给定的类,有问题直接内部抛出异常;并判断类是否被初始化,未初始化则去初始化,实现逻辑如下:
	checkIsKnownClass(cls);
	
	if (slowpath(!cls->isRealized())) {
    
    
	    cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
	}
	
	if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
    
    
	    cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
	}
一、getMethodNoSuper_nolock 二分查找方法列表
  • getMethodNoSuper_nolock 源码实现如下:
	static method_t *
	getMethodNoSuper_nolock(Class cls, SEL sel)
	{
    
    
	    for (auto mlists = cls->data()->methods.beginLists(), 
	              end = cls->data()->methods.endLists(); 
	         mlists != end;
	         ++mlists)
	    {
    
    
	        method_t *m = search_method_list(*mlists, sel);
	        if (m) return m;
	    }
	    return nil;
	}
  • 源码分析:
    • getMethodNoSuper_nolock 遍历 cls->methods 存放到 mlists 中;
    • 通过 search_method_list 函数查找 sel 对应的 method
    • search_method_list 主要依赖于 findMethodInSortedMethodList
    • findMethodInSortedMethodList 使用 二分法 list 中查找 method
  • findMethodInSortedMethodList 源码实现如下:
	ALWAYS_INLINE static method_t *
	findMethodInSortedMethodList(SEL key, const method_list_t *list)
	{
    
    
	    ASSERT(list);
	
	    const method_t * const first = &list->first;
	    const method_t *base = first;
	    const method_t *probe;
	    uintptr_t keyValue = (uintptr_t)key; //key 等于 say666
	    uint32_t count;
	    for (count = list->count; count != 0; count >>= 1) {
    
    
	        probe = base + (count >> 1); 
	        uintptr_t probeValue = (uintptr_t)probe->name;
	        
	        if (keyValue == probeValue) {
    
     
	            while (probe > first && keyValue == (uintptr_t)probe[-1].name){
    
    
	                probe--;
	            }
	            return (method_t *)probe;
	        }
	        
	        if (keyValue > probeValue) {
    
     
	            base = probe + 1;
	            count--;
	        }
	    }
	    return nil;
	}
  • 源码分析:
    • probe = base + (count >> 1) :base 是 low,count 是 max,probe 是 middle,即中间二分开始查询:首地址 + 下标(移动到中间位置(count >> 1 左移1位即 count/2 = 4));
    • keyValue == probeValue :如果查找的 key 的 keyvalue 等于中间位置(probe)的probeValue,则直接返回中间位置;
    • while (probe > first && keyValue == (uintptr_t)probe[-1].name) :排除分类重名方法;
    • keyValue > probeValue :如果 keyValue 大于 probeValue,就往 probe (即中间位置)右边查找;
  • 二分查找原理:
    • 从第一次查找开始,每次都取 中间位置 ,与需要查找的 key 的 value 值作比较;
    • 如果相等,则需要 排除分类方法 ,然后将查询到的位置的方法实现返回;
    • 如果不相等,则需要 继续二分查找
    • 如果循环至count = 0还是没有找到,则直接返回 nil
  • 调用 search_method_list_inline 方法对 本类方法列表 进行查找:
	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;
	}
  • 如果找到了则进入本方法 done() ,调用 log_and_fill_cache 方法:
	 done:
    log_and_fill_cache(cls, imp, sel, inst, curClass);
    runtimeLock.unlock();
  • log_and_fill_cache 方法:利用 cache_fill 方法 写入到缓存里面,为了下次直接从缓存里面快速查找到;
	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
	    // objc_msgSend -> 二分查找自己 -> cache_fill -> objc_msgSend
	    //
	    cache_fill(cls, sel, imp, receiver);
	}
二、cache_getImp 父类缓存查找
  • 查找本类的方法列表:如果找不到,就递归查找父类的缓存,调用 cache_getImp 方法找到父类;
	// Superclass cache.
	imp = cache_getImp(curClass, sel);
  • cache_getImp 方法源码实现如下:
	STATIC_ENTRY _cache_getImp
	
	    GetClassFromIsa_p16 p0
	    CacheLookup GETIMP, _cache_getImp
	
	LGetImpMiss:
	    mov p0, #0
	    ret
	
	    END_ENTRY _cache_getImp
  • 分析说明: ache_getImp 是从 父类的缓存中 找我们传入的 sel 方法,如果父类中缓存没有找到,就 break跳出for循环 。如果父类中缓存中找到了这个方法,就 加到本类的缓存中 ,方便下次直接快速查找。(我相信大家已经发现for循环里只有一个条件,相当于是一个死循环)
  • 流程总结分析:
    • 通过 getMethodNoSuper_nolock 在本类中查找 meth ,如果找到meth进 goto done 缓存起来;
    • 如果没找到meth,通过curClass = curClass->superclass把curClass指向父类,如果 curClass 为 nil,说明找不到方法,imp = forward_imp然后结束循环(attempts=0说明循环已到上限,进行报错);
    • 通过 cache_getImp(curClass, sel) 方法在父类缓存中查找,如果找不到就break跳出for循环。如果父类中缓存中找到了这个方法,就加到本类的缓存中,方便下次直接快速查找;
    • 如果 imp == forward_imp ,结束循环;
    • 如果没找到方法,循环结束后会进入 resolveMethod_locked 去做一次动态决议。
三、resolveMethod_locked 动态决议
  • resolveMethod_locked 源码实现如下:
	// No implementation found. Try method resolver once.
	
	if (slowpath(behavior & LOOKUP_RESOLVER)) {
    
    
	  behavior ^= LOOKUP_RESOLVER;
	  return resolveMethod_locked(inst, sel, cls, behavior);
	}
	
	...分割线
	
	static NEVER_INLINE IMP
	resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
	{
    
    
	    runtimeLock.assertLocked();
	    ASSERT(cls->isRealized());
	
	    runtimeLock.unlock();
	   // 实例方法调用
	    if (! cls->isMetaClass()) {
    
    
	        // try [cls resolveInstanceMethod:sel]
	        resolveInstanceMethod(inst, sel, cls);
	    } 
	    else {
    
    
	      
	        // try [nonMetaClass resolveClassMethod:sel]
	        // and [cls resolveInstanceMethod:sel]
	        // 类方法调用
	        resolveClassMethod(inst, sel, cls);
	        if (!lookUpImpOrNil(inst, sel, cls)) {
    
    
	            resolveInstanceMethod(inst, sel, cls);
	        }
	    }
	
	    // chances are that calling the resolver have populated the cache
	    // so attempt using it
	    return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
	}
  • 可以看到实例方法会调用 resolveInstanceMethod ,类方法会调用 resolveClassMethod 。那么resolveInstanceMethod做了什么呢?
	static void resolveInstanceMethod(id inst, SEL sel, Class cls)
	{
    
    
	    runtimeLock.assertUnlocked();
	    ASSERT(cls->isRealized());
	    SEL resolve_sel = @selector(resolveInstanceMethod:);
	    // 判断系统是否实现SEL_resolveInstanceMethod方法
	    // 即+(BOOL)resolveInstanceMethod:(SEL)sel
	    // 继承自NSObject的类,默认实现,返回NO
	    if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
    
    
	        // Resolver not implemented.
	        // 不是NSObject的子类,也未实现+(BOOL)resolveInstanceMethod:(SEL)sel,
	        // 直接返回,没有动态解析的必要
	        return;
	    }
	    // 系统补偿一次机会 - 针对 sel 来操作
	    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
	    bool resolved = msg(cls, resolve_sel, sel);
	
	    // Cache the result (good or bad) so the resolver doesn't fire next time.
	    // +resolveInstanceMethod adds to self a.k.a. cls
	    // 再次寻找IMP
	    IMP imp = lookUpImpOrNil(inst, sel, cls);
	    // 只有这用了resolved, 所以返回NO或YES不影响forward
	    if (resolved  &&  PrintResolving) {
    
    
	        if (imp) {
    
    
	            _objc_inform("RESOLVE: method %c[%s %s] "
	                         "dynamically resolved to %p", 
	                         cls->isMetaClass() ? '+' : '-', 
	                         cls->nameForLogging(), sel_getName(sel), imp);
	        }
	        else {
    
    
	            // Method resolver didn't add anything?
	            _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
	                         ", but no new implementation of %c[%s %s] was found",
	                         cls->nameForLogging(), sel_getName(sel), 
	                         cls->isMetaClass() ? '+' : '-', 
	                         cls->nameForLogging(), sel_getName(sel));
	        }
	    }
	}
  • resolveClassMethod 实现如下:
	static void resolveClassMethod(id inst, SEL sel, Class cls)
	{
    
    
	    runtimeLock.assertUnlocked();
	    ASSERT(cls->isRealized());
	    ASSERT(cls->isMetaClass());
	
	    if (!lookUpImpOrNil(inst, @selector(resolveClassMethod:), cls)) {
    
    
	        // Resolver not implemented.
	        return;
	    }
	
	    Class nonmeta;
	    {
    
    
	        mutex_locker_t lock(runtimeLock);
	        nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
	        // +initialize path should have realized nonmeta already
	        if (!nonmeta->isRealized()) {
    
    
	            _objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
	                        nonmeta->nameForLogging(), nonmeta);
	        }
	    }
	    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
	    bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);
	
	    // Cache the result (good or bad) so the resolver doesn't fire next time.
	    // +resolveClassMethod adds to self->ISA() a.k.a. cls
	    IMP imp = lookUpImpOrNil(inst, sel, cls);
	
	    if (resolved  &&  PrintResolving) {
    
    
	        if (imp) {
    
    
	            _objc_inform("RESOLVE: method %c[%s %s] "
	                         "dynamically resolved to %p", 
	                         cls->isMetaClass() ? '+' : '-', 
	                         cls->nameForLogging(), sel_getName(sel), imp);
	        }
	        else {
    
    
	            // Method resolver didn't add anything?
	            _objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
	                         ", but no new implementation of %c[%s %s] was found",
	                         cls->nameForLogging(), sel_getName(sel), 
	                         cls->isMetaClass() ? '+' : '-', 
	                         cls->nameForLogging(), sel_getName(sel));
	        }
	    }
	}
  • 由此,可以在+(BOOL)resolveInstanceMethod:(SEL)sel方法中对未实现的方法指定已实现方法的IMP,并添加到类中,实现方法动态解析,防止系统崩溃。
四、objc_msgForward_impcache
  • 如果方法决议仍然没有处理,那么 const IMP forward_imp = (IMP)_objc_msgForward_impcache ;,走到objc_msgForward_impcache方法,objc_msgForward_impcache是汇编实现,以arm64的为例:
    STATIC_ENTRY __objc_msgForward_impcache

    // No stret specialization.
        // 跳转到__objc_msgForward
    b   __objc_msgForward

    END_ENTRY __objc_msgForward_impcache

    ENTRY __objc_msgForward

    adrp    x17, __objc_forward_handler@PAGE
    ldr p17, [x17, __objc_forward_handler@PAGEOFF]
    TailCallFunctionPointer x17
    
    END_ENTRY __objc_msgForward
  • _objc_msgForward_impcache 方法调用 __objc_msgForward 方法, __objc_msgForward 方法 调用 _objc_forward_handler 方法,我们锁定汇编方法__objc_forward_handler,全局搜索发现找不到,原来又是 c++,去掉一个,最终找到了__objc_forward_handler 方法的具体实现:
	// Default forward handler halts the process.
	__attribute__((noreturn, cold)) void
	objc_defaultForwardHandler(id self, SEL sel)
	{
    
    
	    _objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
	                "(no message forward handler is installed)", 
	                class_isMetaClass(object_getClass(self)) ? '+' : '-', 
	                object_getClassName(self), sel_getName(sel), self);
	}
	void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
  • 这就是在开发中经常遇到的错误 unrecognized selector
五、慢速查找流程总结
  • 查找 cache 中是否存在(优化);
  • checkIsKnownClass 检查类是否是已知的类;
  • 如果cls未实现就调用 realizeClassMaybeSwiftAndLeaveLocked 实现(ro、rw);
  • 如果cls未初始化就调用 initializeAndLeaveLocked 初始化(+initialize);
  • 再次尝试从 cache 中查找(防止其它线程已经加入 cache );
  • 尝试从cls中查找方法,找到就调用 log_and_fill_cache 加入到cache中;
  • 遍历cls所有的 superclass ,尝试在superclass中查找方法,找到就加入到 cache 中;
  • 如果没有找到实现,就尝试进行 动态方法解析
  • 如果还没找到实现,就尝试进行 消息转发

猜你喜欢

转载自blog.csdn.net/Forever_wj/article/details/108744818