OC底层->objc_msgSend 消息发送

575 阅读3分钟

Runtime API提供的接口基本都是C语言的,源码由C\C++\汇编语言编写

简介

  • xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m image.png

  • main.m -> main.cpp

  • main.m 调用了一个函数 image.png

  • c++ 文件中的执行 image.png

  • 结论是: [person persontest] 的本质是 调用了 objc_msgSend(person,sel_registerName("persontest"))

  • sel_registerName("persontest") 是runtime的C语言函数等价于@selector(personTest)

  • 所以 objc_msgSend(person,@selector(personTest))

  • 类方法的调用其实也是一样的 image.png

objc_msgSend简介

  • OC中的方法调用,其实都是转换为objc_msgSend函数的调用
    • OC方法调用:给消息接受者发送消息
     MJPerson *person = [[MJPerson alloc] init];
    [person personTest];
    // objc_msgSend(person, @selector(personTest));
    // 消息接收者(receiver):person
    // 消息名称:personTest
    
    [MJPerson initialize];
    // objc_msgSend([MJPerson class], @selector(initialize));
    // 消息接收者(receiver):[MJPerson class]
    // 消息名称:initialize
    
  • objc_msgSend的执行流程可以分为3大阶段
    • 消息发送
    • 动态方法解析
    • 消息转发

objc_msgSend消息发送 源码查看

image.png

  • objc_msgSend的汇编源码具体实现 image.png
    //进入_objc_msgSend
    ENTRY _objc_msgSend
    UNWIND _objc_msgSend, NoFrame
    //p0: p0寄存器的值 p0:存放objc_msgSend的第一个参数 是Self
    //cmp:比较指令
    //这里是判断接受者是不是空
    cmp	p0, #0			// nil check and tagged pointer check 
                               
    #if SUPPORT_TAGGED_POINTERS
    //如果支持 SUPPORT_TAGGED_POINTERS。判断上面的比较结果,是否 ≤ 0,是则跳转到 LNilOrTagged 进行处理。因为在 arm64 下,当为 Tagged pointer 时,最高位是 1,作为有符号数,< 0。  
    //不支持的话,则判断比较结果是否为 0。如果为 0,则跳转到  LReturnZero 进行 nil 的处理。
    b.le	LNilOrTagged		//  (MSB tagged pointer looks negative)                                
    #else
    b.eq	LReturnZero
    #endif
    // 以上都不成立
    // 在 arm64 下,p0 和 x0 是等价的 x0 = self 因为 isa 是
    //objc_object 中只有一个成员  isa, 因此取出指针指向的内容,也就获取到了  isa 的值。
    // p13 = isa 
    ldr	p13, [x0]		
    GetClassFromIsa_p16 p13, 1, x0	// p16 = class
    LGetIsaDone:
    // calls imp or objc_msgSend_uncached
    CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached  //查找缓存

    #if SUPPORT_TAGGED_POINTERS
    LNilOrTagged:
    b.eq	LReturnZero		// nil check
    GetTaggedClass
    b	LGetIsaDone
    // SUPPORT_TAGGED_POINTERS
    #endif

    LReturnZero:
    // x0 is already zero
    mov	x1, #0
    movi	d0, #0
    movi	d1, #0
    movi	d2, #0
    movi	d3, #0
    ret

    END_ENTRY _objc_msgSend
  • 缓存查找 简化一下
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
	//x16寄存器的内容放到x15 就是把 class的地址保存一份
	mov	x15, x16			// stash the original isa
LLookupStart\Function:
	// p1 = SEL, p16 = isa
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
	// 将 cache_t 的地址放入 p10 cache_t
	ldr	p10, [x16, #CACHE]				// p10 = mask|buckets
// 将 cache_t 的地址放入 p11 逻辑右移,高地址向低地址移动48位剩下的16位就是mask
	lsr	p11, p10, #48			// p11 = mask
	//与运行 p10与上掩码得到buckets 存放到p10
	and	p10, p10, #0xffffffffffff	// p10 = buckets
	//w1 其实就是 x1 方法的第二个参数 SEL cmd:SEL & mask 求出对应的index
	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		// 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
	//上面的判断是在不同的架构下的判断运算规则但是整体不变的是
	//p10 里面放了 buckets 散列表
	//p12 里面放了 _cmd & mask = index
	//p13 里面就放了 bucket_t 的地址
	add	p13, p10, p12, LSL #(1+PTRSHIFT)
						// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))

						// do {
// 从定位到的表项地址中,取出 2 个 8 字节数据放到 p17, p9 中。其中 p17 里是 imp,p9 里是 sel。
1:	ldp	p17, p9, [x13], #-BUCKET_SIZE	//     {imp, sel} = *bucket--
// 比较缓存中的 sel 和传入的 _cmd
	cmp	p9, p1				//     if (sel != _cmd) {
// 不相等,跳转到 3 ne = not equal
	b.ne	3f				//         scan more
		
// 命中缓存,调用 imp				//     } else {
2:	CacheHit \Mode				// hit:    call or return imp
						//     }
// 检查 p9 中的 sel 是否为空,若为空,则跳转到 __objc_msgSend_uncached,再进行缓存未命中的查找
3:	cbz	p9, \MissLabelDynamic		//     if (sel == 0) goto Miss;
// 比较取到的缓存项和缓存表地址是否一致,也就是是否是第一项
	cmp	p13, p10			// } while (bucket >= buckets)
//结果如果大于或等于成立就跳到1
	b.hs	1b
.endmacro
  • 缓存中找到返回 imp方法的地址 image.png
  • 因为传参第一个参数是normal image.png
  • x17 = 缓存 imp
  • x10 = 查找到的缓存项地址
  • x1 = sel
  • x16 = class
  • 缓存中没有找到方法则 调用MethodTableLookup
.macro MethodTableLookup
	//保存寄存器
	SAVE_REGS MSGSEND

	// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
	// receiver and selector already in x0 and x1
        //x2 保存了 cls
	mov	x2, x16
        // LOOKUP_INITIALIZE = 1, LOOKUP_RESOLVER = 2, 两者或运算 = 3
	mov	x3, #3
        // 调用 _lookUpImpOrForward 进行查找,最后查找到的 imp 放到 x0 中
	bl	_lookUpImpOrForward

	// IMP in x0
        // 将 imp 放到 x17
	mov	x17, x0
        // 恢复寄存器
	RESTORE_REGS MSGSEND

.endmacro
  • 大佬文章 大佬文章可参考
  • _lookUpImaOrForward 汇编方法去掉下划线才能在C语言中找到 image.png
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())) {

        behavior |= LOOKUP_NOCACHE;
    }

    runtimeLock.lock();

    checkIsKnownClass(cls);

    cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
    runtimeLock.assertLocked();
    curClass = cls;
    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;
}

  • 具体解释请参考这位大佬的文章 因为我用的源码是818.2他的用的是718.2所以有点出入。 参考文章

  • 得出以下流程:

    • 1.自己的cache ->
    • 2.自己的methods ->
    • 3.父亲的cache ->
    • 4.父亲的methods ->
    • 5.父亲的父亲的cache ->
    • 6.父亲的父亲的methods ->
    • 7.nil -> resolveMethod_locked(动态方法解析)

image.png

  • 如果是从class_rw_t中查找方法
    • 已经排序的,二分查找
    • 没有排序的,遍历查找
  • receiver通过isa指针找到receiverClass
  • receiverClass通过superclass指针找到superClass