iOS开发底层之RuntimeObjc_msgSend探究下 - 09

Posted iOS_developer_zhong

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS开发底层之RuntimeObjc_msgSend探究下 - 09相关的知识,希望对你有一定的参考价值。

文章目录


一、上篇文章补充

1.GetClassFromIsa_p16 宏解读

  1. 上篇文章,没有解读此处, 源码如下。
.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */

#if SUPPORT_INDEXED_ISA
	// Indexed isa
	mov	p16, \\src			// optimistically set dst = src
	tbz	p16, #ISA_INDEX_IS_NPI_BIT, 1f	// done if not non-pointer isa
	// isa in p16 is indexed
	adrp	x10, _objc_indexed_classes@PAGE
	add	x10, x10, _objc_indexed_classes@PAGEOFF
	ubfx	p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS  // extract index
	ldr	p16, [x10, p16, UXTP #PTRSHIFT]	// load class from array
1:

#elif __LP64__
.if \\needs_auth == 0 // _cache_getImp takes an authed class already
	mov	p16, \\src
.else
	// 64-bit packed isa
	ExtractISA p16, \\src, \\auth_address
.endif
#else
	// 32-bit raw isa
	mov	p16, \\src

#endif

.endmacro

上述代码中GetClassFromIsa_p16是定义的一个宏,主要功能为,通过isa找到对应的类;ExtractISA也是个宏定义,功能为将传入的isa&isaMask,得到class,并将class赋给p16。

  1. 得到class后,接下来探索下CacheLookup缓存查找流程。
LGetIsaDone:
	// calls imp or objc_msgSend_uncached
	CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached

二、CacheLookup探索

  1. 源码探索,看看到底如何命中缓存,
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
	
	mov	x15, x16			// stash the original isa
LLookupStart\\Function:
	// p1 = SEL, p16 = isa
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
	ldr	p10, [x16, #CACHE]				// p10 = mask|buckets
	lsr	p11, p10, #48			// p11 = mask
	and	p10, p10, #0xffffffffffff	// p10 = buckets
	and	w12, w1, w11			// x12 = _cmd & mask
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
	ldr	p11, [x16, #CACHE]			// p11 = mask|buckets
#if CONFIG_USE_PREOPT_CACHES
#if __has_feature(ptrauth_calls)
	tbnz	p11, #0, LLookupPreopt\\Function
	and	p10, p11, #0x0000ffffffffffff	// p10 = buckets
#else
	and	p10, p11, #0x0000fffffffffffe	// p10 = buckets
	tbnz	p11, #0, LLookupPreopt\\Function
#endif
	eor	p12, p1, p1, LSR #7
	and	p12, p12, p11, LSR #48		// insert的时候也是右移7位,所以取一样的, x12 = (_cmd ^ (_cmd >> 7)) & mask
#else
	and	p10, p11, #0x0000ffffffffffff	// p10 = buckets
	and	p12, p1, p11, LSR #48		// x12 = _cmd & mask
#endif // CONFIG_USE_PREOPT_CACHES
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
	ldr	p11, [x16, #CACHE]				// p11 = mask|buckets
	and	p10, p11, #~0xf			// p10 = buckets
	and	p11, p11, #0xf			// p11 = maskShift
	mov	p12, #0xffff
	lsr	p11, p12, p11			// p11 = mask = 0xffff >> p11
	and	p12, p1, p11			// x12 = _cmd & mask
#else
#error Unsupported cache mask storage for ARM64.
#endif
     // 循环流程  p13,对应下标,第一个要查的bucket(sel - imp)
	add	p13, p10, p12, LSL #(1+PTRSHIFT)
						// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))

						// do 
1:	ldp	p17, p9, [x13], #-BUCKET_SIZE	//     imp, sel = *bucket--
	cmp	p9, p1				//     if (sel != _cmd) 
	b.ne	3f				//         scan more,不等于就去第三部
						//      else 
2:	CacheHit \\Mode				// hit:    call or return imp,缓存命中
						//     
3:	cbz	p9, \\MissLabelDynamic		//     if (sel == 0) goto Miss; // 没有找到
	cmp	p13, p10			//  while (bucket >= buckets)   循环条件, 是否超出了buckets内存地址
	b.hs	1b

	// wrap-around:
	//   p10 = first bucket
	//   p11 = mask (and maybe other bits on LP64)
	//   p12 = _cmd & mask

#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
	add	p13, p10, w11, UXTW #(1+PTRSHIFT)
						// p13 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
	add	p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
						// p13 = buckets + (mask << 1+PTRSHIFT)
						// see comment about maskZeroBits
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
	add	p13, p10, p11, LSL #(1+PTRSHIFT)
						// p13 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif
	add	p12, p10, p12, LSL #(1+PTRSHIFT)
						// p12 = first probed bucket

						// do 
4:	ldp	p17, p9, [x13], #-BUCKET_SIZE	//     imp, sel = *bucket--
	cmp	p9, p1				//     if (sel == _cmd)
	b.eq	2b				//         goto hit
	cmp	p9, #0				//  while (sel != 0 &&
	ccmp	p13, p12, #0, ne		//     bucket > first_probed)
	b.hi	4b

上述流程,暂且只分析CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS环境。 重要步骤为 解释 +源码来描述。

接本篇第一大点,获取到class类对象地址后,去进行缓存查找方法。 步骤如下:

  1. 对类对象地址,进行指针平移16个字节,得到cache_t的首地址,这个可以看前面objc_class的内存结构成员变量表结构, 在cache_t之前,有 isa,还有superclass,所以这里平移16个字节。
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
	ldr	p10, [x16, #CACHE]				// p10 = mask|buckets
	lsr	p11, p10, #48			// p11 = mask
  1. 在真机环境下, maskbuckets共占用64位,mask占高16位, buckets占低48位, 现在为了得到buckets,所以进行了掩码运算, 源码为and p10, p11, #0x0000ffffffffffff, 然后找到buckets后,就涉及到了 cache_hash算法,源码如下:
#if __has_feature(ptrauth_calls)
	tbnz	p11, #0, LLookupPreopt\\Function
	and	p10, p11, #0x0000ffffffffffff	// p10 = buckets
#else
	and	p10, p11, #0x0000fffffffffffe	// p10 = buckets
	tbnz	p11, #0, LLookupPreopt\\Function
#endif  
    // cache_hash算法
	eor	p12, p1, p1, LSR #7         // p1 = _cmd
	and	p12, p12, p11, LSR #48		// insert的时候也是右移7位,所以取一样的, x12 = (_cmd ^ (_cmd >> 7)) & mask
#else
	and	p10, p11, #0x0000ffffffffffff	// p10 = buckets
	and	p12, p1, p11, LSR #48		// x12 = _cmd & mask
#endif // CONFIG_USE_PREOPT_CACHES
  1. cache_hash算法如下:
    3.1 p1(_cmd)右移7位,进行异或运算, 然后给 p12 , 然后p11右移48位,得到mask ,然后mask与p12进行与操作获取下标 , 然后赋值给 p12 , 通过上面的操作,我们记录下相关运算后的各参数的结果。
  • p11 = _bucketsAndMaybeMask
  • p10 = buckets
  • p12 = 经过运算后查找的方法的hash下标
  1. 得到了hash下标, 然后buckets的首地址也知道,现在就开始要寻找bucket
     // 循环流程  p13,对应下标,第一个要查的bucket(sel - imp)  PTRSHIFT = 3 
	add	p13, p10, p12, LSL #(1+PTRSHIFT)
						// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))

算法解读为 , PTRSHIFT = 3, 所以 相当于 对 p12进行左移16位, (sel 8位, imp8位), p10是buckets收地址,加上 p12(经过运算,相当于16的倍数位移)。

  1. 下面进入了一个循环查找过程, 进行sel对比, 直到超出了buckets范围。
						// do 
1:	ldp	p17, p9, [x13], #-BUCKET_SIZE	//     imp, sel = *bucket--
	cmp	p9, p1				//     if (sel != _cmd) 
	b.ne	3f				//         scan more,不等于就去第三部
						//      else 
2:	CacheHit \\Mode				// hit:    call or return imp,缓存命中
						//     
3:	cbz	p9, \\MissLabelDynamic		//     if (sel == 0) goto Miss; // 没有找到
	cmp	p13, p10			//  while (bucket >= buckets)   循环条件, 是否超出了buckets内存地址
	b.hs	1b
  1. 如果上面的循环查找sel,没有找到,就会走到下面的查找流程。 从最后mask的存储位置往前查找。
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
	add	p13, p10, w11, UXTW #(1+PTRSHIFT)
						// p13 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
	add	p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
						// p13 = buckets + (mask << 1+PTRSHIFT)
						// see comment about maskZeroBits
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
	add	p13, p10, p11, LSL #(1+PTRSHIFT)
						// p13 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif
  1. 再次,开启了一个循环, 这个循环是从最后一个位置,往前进行查询, 首先重新设置p12的下标。 然后开启查找循环。
	add	p12, p10, p12, LSL #(1+PTRSHIFT)
						// p12 = first probed bucket

						// do 
4:	ldp	p17, p9, [x13], #-BUCKET_SIZE	//     imp, sel = *bucket--
	cmp	p9, p1				//     if (sel == _cmd)
	b.eq	2b				//         goto hit
	cmp	p9, #0				//  while (sel != 0 &&
	ccmp	p13, p12, #0, ne		//     bucket > first_probed)
	b.hi	4b
// 上述循环,没有走完,就会走到 MissLabelDynamic流程
LLookupEnd\\Function:
LLookupRecover\\Function:
	b	\\MissLabelDynamic
  1. 命中缓存的方法 CacheHit探索
// 参数说明:CacheHit: x17 = cached IMP, x10 = address of buckets, x1 = SEL, x16 = isa
.macro CacheHit
.if $0 == NORMAL
	TailCallCachedImp x17, x10, x1, x16	// authenticate and call imp
.elseif $0 == GETIMP
	mov	p0, p17
	cbz	p0, 9f			// don't ptrauth a nil imp
	AuthAndResignAsIMP x0, x10, x1, x16	// authenticate imp and re-sign as IMP
9:	ret				// return IMP
.elseif $0 == LOOKUP
	// No nil check for ptrauth: the caller would crash anyway when they
	// jump to a nil IMP. We don't care if that jump also fails ptrauth.
	AuthAndResignAsIMP x17, x10, x1, x16	// authenticate imp and re-sign as IMP
	cmp	x16, x15
	cinc	x16, x16, ne			// x16 += 1 when x15 != x16 (for instrumentation ; fallback to the parent class)
	ret				// return imp via x17
.else
.abort oops
.endif
.endmacro
  1. 缓存找不到, lookUpImpOrForward - 慢速 - methodlsit

三、lookUpImpOrForward 探索

1 lookUpImpOrForward 慢速查找实现

  1. 一起来看下这个方法里面是如何实现的, 上源码:
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();
    runtimeLock.lock();  //1.多线程保护,进行安全加锁
    checkIsKnownClass(cls); //2.判断当前类有没有进行注册到缓存表, 注册类
    cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);  //3. ro ,rw的处理,按照isa流程递归初始化,并赋值。 为了方便后续的查方法。自身方法找不到,就找父类, 类方法就找元类。 
    // ro ,rw 存放了属性与方法列表。  
    // runtimeLock may have been dropped but is now locked again
    runtimeLock.assertLocked();
    curClass = cls;
 

    for (unsigned attempts = unreasonableClassCount();;) 
        if (curClass->cache.isConstantOptimizedCache(/* strict */true))             // 4. 共享缓存再查找一遍这个SEL, 是为了防止上面进行了一系列操作,刚好插入了这个方法。 
#if CONFIG_USE_PREOPT_CACHES
            imp = cache_getImp(curClass, sel);
            if (imp) goto done_unlock;
            curClass = curClass->cache.preoptFallbackClass();
#endif
         else 
            // curClass method list.
// 5. 重要: 使用了二分查找法,去寻找对应的sel , 最终的算法实现探索路径为 getMethodNoSuper_nolock   -》 search_method_list_inline  -》 findMethodInSortedMethodList
            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.
        // 6. 去父类中查找方法
        // 快速 -》 慢速 -》父类的父类 (递归操作 找到nil为止) -》 最终返回 forward_imp
        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;



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)
    
     
        method_t *m = search_method_list_inline(*mlists, sel);
        if (m) return m;
    

    return nil;


 说明: 慢速查找方法流程
 1. 查自己的 methodList - sel -> imp
 2. 查父类 -> NSObject -> nil -> 跳出来。 (对象方法)
 3.  查元类 (类方法才会走这个流程查找方法)
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;
    
    // >> 1  举例:   1000  >> 1  = 0100 = 4 ,减半 
	// 设计很巧妙,后面再详细写下步骤
    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;

2 总结下lookUpImpOrForward方法主要做了什么:

  1. 判断类是否初始化
  2. 判断类是否注册到缓存表
  3. 对类进行赋值 RO RW ,递归实现父类和元类
  4. 从缓存中查找imp ,有的话就直接返回
  5. 采用二分查找法从方法列表中查找imp ,找到了,就插入到缓存表,下次访问就可以快速访问了。
  6. 判断当前类的父类是否存在, 不存在就进入消息转发
  7. 父类存在, 就开始快速查找 -》 慢速查找 -》 递归父类 ,直到nil ,找到imp就返回, 反之,就返回forward_imp

以上是关于iOS开发底层之RuntimeObjc_msgSend探究下 - 09的主要内容,如果未能解决你的问题,请参考以下文章

iOS开发底层之RuntimeObjc_msgSend探究 - 08

iOS开发之结构体底层探索

iOS开发底层之对象的本质-04

iOS开发底层之内存对齐详解-03

iOS开发底层之NSObject-alloc源码分析-02

iOS开发底层之消息的快速与慢速转发 - 11