底层原理(六)方法探索之快速(一)

219 阅读3分钟

问题

  • OC 方法的调用本质是什么
  • OC 函数调用是什么样子的?
  • 方法发送的方式有哪几种?
  • 方法的快速查找流程?

OC 方法调用的本质是什么

  1. 我们新建一个项目,创建一个LBPerson , 然后我们在main 函数里面进行方法的调用:

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            LBPerson *person = [[LBPerson alloc] init];
            [person sayHello];
            
        }
        return 0;
    }
    
  2. 我们Clang一下:clang -rewrite-objc main.m -o main.cpp

    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
    
            LBPerson *person = ((LBPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((LBPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LBPerson"), sel_registerName("alloc")), sel_registerName("init"));
            ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayHello"));
    
        }
        return 0;
    }
    static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
    
  3. 看起来有点麻烦我们去掉不必要的强转:

    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
    
            LBPerson *person = objc_msgSend((id)objc_getClass("LBPerson"), sel_registerName("alloc")), sel_registerName("init");
            objc_msgSend((id)person, sel_registerName("sayHello"));
    
        }
        return 0;
    }
    static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
    
  4. 我们可以看出本质是调用了 objc_msgSend 函数,我们可以联想到方法里面有两个默认的参数,一个是id self,还有一个是sel _cmd。这里应该就呼应上了,大概就清楚是怎么调用方法的了。那么objc_msg 到底做了什么呢?还有一个问题函数的调用本地是什么样子的呢?我们修改步骤1 的代码如下:

    void testFunc() {
        printf("hello");
    }
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            LBPerson *person = [[LBPerson alloc] init];
            [person sayHello];
            
            testFunc();
        }
        return 0;
    }
    

    接着 我们继续clang一下:

借此我们也可以看出方法 和 函数的区别了。我们清晰的看到函数并没有做什么特别操作。

那么方法的调用有多少种呢?

我们在探究前,有个东西需要关闭一下,否则可能会出现如下问题:

我们只要关闭这个东西

进入正题

调用方法:

      LBPerson *person = [[LBPerson alloc] init];
        LBStudent *student = [[LBStudent alloc] init];
        [person sayHello];
        
        // 给对象发送消息
        objc_msgSend(person,sel_registerName("sayHello"));
        objc_msgSend(person,@selector(sayHello));
        // 给类发送消息
        objc_msgSend(objc_getClass("LBPerson"), @selector(sayHaHa));
        
        // 给父类发送消息
        struct objc_super LBSuper;
        LBSuper.receiver = student;
        LBSuper.super_class = [LBPerson class];
        objc_msgSendSuper(&LBSuper, @selector(sayHello));
        
        struct objc_super LBSuper2;
        LBSuper2.receiver = student;
        LBSuper2.super_class = class_getSuperclass(object_getClass(student));
        objc_msgSendSuper(&LBSuper2, @selector(sayHello));

打印方法:

2020-01-19 15:05:45.135074+0800 msgSendDemo[69339:82747636] func ---- -[LBPerson sayHello] ---
2020-01-19 15:05:45.135458+0800 msgSendDemo[69339:82747636] func ---- -[LBPerson sayHello] ---
2020-01-19 15:05:45.135489+0800 msgSendDemo[69339:82747636] func ---- -[LBPerson sayHello] ---
2020-01-19 15:05:45.135512+0800 msgSendDemo[69339:82747636] -----+[LBPerson sayHaHa] ---
2020-01-19 15:05:45.135533+0800 msgSendDemo[69339:82747636] func ---- -[LBPerson sayHello] ---
2020-01-19 15:05:45.135559+0800 msgSendDemo[69339:82747636] func ---- -[LBPerson sayHello] ---

方法的调用流程

我们知道了方法的本质是objc_msdSend 那么我们就来跟一下

紧接着我们跟进去

这个时候我们跟进去了 接着我们发现跟不进去了。这个时候我们跟一下苹果官方开源的objc750 源码,我们在汇编代码里面找到了这么一句话。

紧接着我们往下读:

	ENTRY _objc_msgSend
	UNWIND _objc_msgSend, NoFrame

	cmp	p0, #0			// nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
	b.le	LNilOrTagged		//  (MSB tagged pointer looks negative)
#else
	b.eq	LReturnZero
#endif
   // 拿到我门上面的对象的isa。
	ldr	p13, [x0]		// p13 = isa
	//为什么到p16
	GetClassFromIsa_p16 p13		// p16 = class
LGetIsaDone:
// 进行缓存的搜所
	CacheLookup NORMAL		// calls imp or objc_msgSend_uncached

我们来看一下:GetClassFromIsa_p16

下面的代码里 为什么平移16 位? Isa + superClass 是16位。

.macro GetClassFromIsa_p16 /* src */

#if SUPPORT_INDEXED_ISA
	// Indexed isa
	mov	p16, $0			// 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__
	// 64-bit packed isa
	// 取面具,蒙版
	and	p16, $0, #ISA_MASK 

#else
	// 32-bit raw isa
	mov	p16, $0

#endif

.endmacro

我们获得isa 之后,我们就进行方法的查找

.macro CacheLookup
	// p1 = SEL, p16 = isa
	ldp	p10, p11, [x16, #CACHE]	// p10 = buckets, p11 = occupied|mask
#if !__LP64__
	and	w11, w11, 0xffff	// p11 = mask
#endif
	// 取方法下标
	and	w12, w1, w11		// x12 = _cmd & mask
	add	p12, p10, p12, LSL #(1+PTRSHIFT) ,移动相应的位置得到相对正确的地址
		             // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))

	ldp	p17, p9, [x12]		// {imp, sel} = *bucket
1:	cmp	p9, p1			// if (bucket->sel != _cmd)
	b.ne	2f			//     scan more 走流程2
	CacheHit $0			// call or return imp
	
2:	// not hit: p12 = not-hit bucket
	CheckMiss $0			// miss if bucket->sel == 0
	cmp	p12, p10		// wrap if bucket == buckets
	b.eq	3f 走流程3
	ldp	p17, p9, [x12, #-BUCKET_SIZE]!	// {imp, sel} = *--bucket
	b	1b			// loop

3:	// wrap: p12 = first bucket, w11 = mask ,进行缓存一下
	add	p12, p12, w11, UXTW #(1+PTRSHIFT)
		                        // p12 = buckets + (mask << 1+PTRSHIFT)

	// Clone scanning loop to miss instead of hang when cache is corrupt.
	// The slow path may detect any corruption and halt later.

	ldp	p17, p9, [x12]		// {imp, sel} = *bucket
1:	cmp	p9, p1			// if (bucket->sel != _cmd)
	b.ne	2f			//     scan more
	CacheHit $0			// call or return imp
	
2:	// not hit: p12 = not-hit bucket
	CheckMiss $0			// miss if bucket->sel == 0
	cmp	p12, p10		// wrap if bucket == buckets
	b.eq	3f
	ldp	p17, p9, [x12, #-BUCKET_SIZE]!	// {imp, sel} = *--bucket
	b	1b			// loop

3:	// double wrap
	JumpMiss $0 // 没有就走jumpMiss
	
.endmacro