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

Runtime

一、什么是 runtime ?
  • Objective-C 语言将尽可能多的决策从 编译时和链接时 推迟到运行时。只要有可能,它就 动态 地做事情,这意味着该语言不仅需要一个编译器,还需要一个 运行时系统 来执行编译后的代码。运行时系统作为 Objective-C 语言的一种操作系统,它使语言起作用。
  • 因为 Objc 是一门动态语言,所以它总是想办法把一些决定工作从编译连接推迟到 运行时 。也就是说只有编译器是不够的,还需要一个运行时系统 (runtime system) 来执行编译后的代码。这就是 Objective-C Runtime 系统存在的意义,它是整个 Objc 运行框架的一块基石。
  • runtime 简称运行时,OC 就是运行时机制,其中最主要的是 消息机制 。对于 C 语言,函数的调用在编译的时候会决定调用哪个函数。对于 OC 的函数,属于 动态调用过程 ,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。
  • runtime 基本是用 C 和汇编 写的,可见苹果为了动态系统的高效而作出的努力。
二、runtime 版本
  • runtime 有两个版本:⼀个 Legacy 版本(早期版本) ,⼀个 Modern 版本(现⾏版本) ;
  • 早期版本对应的编程接⼝: Objective-C 1.0;
    现⾏版本对应的编程接⼝: Objective-C 2.0。
  • 头文件:
	#import <objc/message.h>
	#import <objc/runtime.h>
三、 runtime 运行方式
  • 直接在 OC 层进行交互:比如 @selector;
  • NSObjct 的:NSSelectorFromString 方法;
  • runtime API: sel_registerName。
    在这里插入图片描述
四、runtime 运行编译:方法本质
  • 定义以下代码:
	@interface YDWPerson : NSObject
	
	- (void)goShopping;
	- (void)goPay;
	
	@end
	@implementation YDWPerson
	
	- (void)goShopping {
    
    
	    NSLog(@"Buy Something");
	}
	
	@end
	YDWPerson *person = [YDWPerson alloc];
    [person goShopping];
    [person goPay];
  • 编译以上代码,可以完全通过,但是 run 却不能正常执行。为什么会出现这个问题?我们不难发现,是因为 goPay 这个方法没有被实现,只是做了声明。
  • 修改以上代码如下:
	YDWPerson *person = [YDWPerson alloc];
    [person goShopping];
        
    objc_msgSend(person, sel_registerName("goShopping"));
  • 然后运行,打印如下:
	2020-09-19 00:28:02.688852+0800 YDWObjc[82371:3807707] Buy Something
	2020-09-19 00:28:02.689424+0800 YDWObjc[82371:3807707] Buy Something
  • 我们写的代码在程序运行过程中都会被转化成 runtime 的 C 代码执行,例如上面的 [person goShopping] ;会被转化成 objc_msgSend(person, sel_registerName(“goShopping”)); ;,它们两者其实是等价的。
    在这里插入图片描述

  • 运用 clang 也可以验证这个结论:

	int main(int argc, const char * argv[]) {
    
    
	    /* @autoreleasepool */ {
    
     __AtAutoreleasePool __autoreleasepool; 
	    
	        YDWPerson *person = ((YDWPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("YDWPerson"), sel_registerName("alloc"));
	        ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("goShopping"));
	        
	    }
	    return 0;
	}
  • 由此可见,真正发送消息的地方是 objc_msgSend ,这个方法有基本的两个参数,第一个参数是 消息的接收者为 id 类型 ,第二个参数是 方法编号为 SEL 类型
  • objc_msgSendSuper 的方法实现如下:
    有两个参数 (结构体,sel) ,其结构体类型是 objc_super 定义的结构体对象,且需要指定 receiver super_class 两个属性;
	OBJC_EXPORT id _Nullable
	objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
	    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
	#endif
  • objc_super 如下:
	#define OBJC_SUPER
	
	/// Specifies the superclass of an instance. 
	struct objc_super {
    
    
	    /// Specifies an instance of a class.
	    __unsafe_unretained _Nonnull id receiver;
	
	    /// Specifies the particular superclass of the instance to message. 
	#if !defined(__cplusplus)  &&  !__OBJC2__
	    /* For compatibility with old objc-runtime.h header */
	    __unsafe_unretained _Nonnull Class class;
	#else
	    __unsafe_unretained _Nonnull Class super_class;
	#endif
	    /* super_class is the first class to search */
	};
	#endif
  • 当一个方法被调用的时候,编译器会根据方法调用的类型生成一个 底层函数 。 调用父类方法生成 objc_msgSendSuper() 函数,非父类方法生成 objc_msgSend() 函数。如果返回值是结构类型,则使用 objc_msgSendSuper_stret 或者 objc_msgSend_stret

objc_msgSend

一、objc_msgSend 消息发送

继续使用上面的代码示例,打印出 goShopping 的实现:

  • 向 person 发送 goShopping 方法:
	YDWPerson *person = [YDWPerson alloc];        
    objc_msgSend(person, sel_registerName("goShopping"));
  • 向 YDWPerson 发送 goShopping 方法;
	objc_msgSend((id)objc_getClass("YDWPerson"), sel_registerName("goShopping"));
  • 向父类 YDWPerson 发送 goShopping 消息;
	YDWPerson *person = [YDWPerson alloc];
    struct objc_super ydwSuper;
    ydwSuper.receiver = person;
    ydwSuper.super_class = YDWPerson.class;
    objc_msgSendSuper(&ydwSuper, sel_registerName("goShopping"));
  • 上面都会打印出:
	2020-09-19 00:57:36.494819+0800 YDWObjc[82683:3833093] Buy Something
	2020-09-19 00:57:36.495416+0800 YDWObjc[82683:3833093] Buy Something
	2020-09-19 00:57:36.495472+0800 YDWObjc[82683:3833093] Buy Something
	2020-09-19 00:57:36.495485+0800 YDWObjc[82683:3833093] Buy Something
  • objc_msgSend() :
    • 它有两个默认参数 id 类型的 self 和 SEL 类型的 _cmd ,其中 self 指向 消息接收者 , _cmd 是 方法选择器 。如果需要传入更多的参数,可以拼接在这两个参数的后面。
    • C 语言的函数指针直接保存了 方法的地址 ,不同于 C 语言的是,SEL 保存的是 消息主体 ,方法 以 SEL 作为 索引 ,通过 SEL 找到 IMP (函数指针)以完成 消息的发送
	objc_msgSend(id _Nullable self, SEL _Nonnull _cmd, ...)
二、objc_msgSend 的实现
  • objc_msgSend() 消息发送的过程就是 通过 SEL 查找 IMP 的过程
  • objc_msgSend() 是用 汇编语言 实现的,使用汇编实现的优势是:
    • 消息发送的过程需要足够的快速,高级语言在执行的时候都是需要翻译成汇编语言,经过编译成被机器识别的 二进制文件 ,使用汇编可以省去这一翻译过程,可以更快速被机器识别;
    • 对于消息的发送,存在很多未知的参数,这有很多不确定性,使用 汇编的寄存器 要比 C 或者 C++ 表现好的多。
三、objc_msgSend 汇编源码分析
  • objc_msgSend 是消息发送的源码入口,其使用汇编实现的, _objc_msgSend 主要是获取接收者的 isa 信息,源码实现如下:
	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
		ldr	p13, [x0]		// p13 = isa
		GetClassFromIsa_p16 p13		// p16 = class
	LGetIsaDone:
		// calls imp or objc_msgSend_uncached
		CacheLookup NORMAL, _objc_msgSend
	
	#if SUPPORT_TAGGED_POINTERS
	LNilOrTagged:
		b.eq	LReturnZero		// nil check
	
		// tagged
		adrp	x10, _objc_debug_taggedpointer_classes@PAGE
		add	x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
		ubfx	x11, x0, #60, #4
		ldr	x16, [x10, x11, LSL #3]
		adrp	x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE
		add	x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF
		cmp	x10, x16
		b.ne	LGetIsaDone
	
		// ext tagged
		adrp	x10, _objc_debug_taggedpointer_ext_classes@PAGE
		add	x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
		ubfx	x11, x0, #52, #8
		ldr	x16, [x10, x11, LSL #3]
		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
	
		ENTRY _objc_msgLookup
		UNWIND _objc_msgLookup, NoFrame
		cmp	p0, #0			// nil check and tagged pointer check
  • 源码分析如下:
    • cmp p0, #0 :p0 和空对比,即判断接收者是否存在,其中 p0 是 objc_msgSend 的第一个参数,即消息接收者 receiver;
    • b.eq LReturnZero : p0 等于 0 时,直接返回空;
    • ldr p13, [x0] :p0 即 receiver 流程,根据对象拿出 isa ,即从 x0寄存器 指向的地址取出 isa,存入 p13 寄存器
    • GetClassFromIsa_p16 p13 :在64位架构下通过 p16 = isa(p13) & ISA_MASK,拿出 shiftcls 信息,得到 class 信息;
    • CacheLookup NORMAL, _objc_msgSend :如果有 isa,执行 CacheLookup(即缓存)查找流程 ,也就是 sel-imp 快速查找流程
  • objc_msgSend 消息入口总结如下:
    • ① 判断 objc_msgSend 方法的第一个参数 receiver 是否为空:
      如果 SUPPORT_TAGGED_POINTERS ,跳转至 LNilOrTagged
      如果 TAGGED_POINTERS 为空,则直接返回空,即 LReturnZero
      如果 TAGGED_POINTERS 不为空,则处理 TAGGED_POINTERS 的 isa,执行 CacheLookup NORMAL
    • ② 如果 receiver 不为空,并不支持 SUPPORT_TAGGED_POINTERS,那么就从 receiver 中取出 isa 存入 p13 寄存器 ,通过 GetClassFromIsa_p16 (arm64 架构)中的 isa & ISA_MASK 获取 shiftcls 位域 的类信息,即 class,执行 CacheLookup NORMAL
  • GetClassFromIsa_p16 的实现如下:
	#if SUPPORT_INDEXED_ISA
		.align 3
		.globl _objc_indexed_classes
	_objc_indexed_classes:
		.fill ISA_INDEX_COUNT, PTRSIZE, 0
	#endif
	
	.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
四、CacheLookup 缓存查找分析(快速查找)
  • ① 通过 cache 首地址平移 16 字节(在 objc_class 中,首地址距离 cache 正好 16 字节,即 isa 占 8 字节,superClass 占 8 字节)获取 cahce,cache 中高 16 位存 mask,低 48 位存 buckets,即 p11 = cache:
	.macro CacheLookup
		//
		// Restart protocol:
		//
		//   As soon as we're past the LLookupStart$1 label we may have loaded
		//   an invalid cache pointer or mask.
		//
		//   When task_restartable_ranges_synchronize() is called,
		//   (or when a signal hits us) before we're past LLookupEnd$1,
		//   then our PC will be reset to LLookupRecover$1 which forcefully
		//   jumps to the cache-miss codepath which have the following
		//   requirements:
		//
		//   GETIMP:
		//     The cache-miss is just returning NULL (setting x0 to 0)
		//
		//   NORMAL and LOOKUP:
		//   - x0 contains the receiver
		//   - x1 contains the selector
		//   - x16 contains the isa
		//   - other registers are set as per calling conventions
		//
	LLookupStart$1:
	
		// p1 = SEL, p16 = isa
		ldr	p11, [x16, #CACHE]				// p11 = mask|buckets
	
	#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
		and	p10, p11, #0x0000ffffffffffff	// p10 = buckets
		and	p12, p1, p11, LSR #48		// x12 = _cmd & mask
	#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
		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
  • 源码分析:
    • ldr p11, [x16, #CACHE]
      p1 = SEL,p16 = isa,#define CACHE (2 * SIZEOF_POINTER),其中 __SIZEOF_POINTER__表示 pointer 的大小 ,即 2*8 = 16;
      p11 = mask|buckets ,从 x16(即 isa )中平移 16 个字节,取出 cache 存入 p11 寄存器,而 isa 距离cache 正好 16 字节:isa(8字节、superClass(8字节);
      cache(mask 高 16 位 + buckets 低 48 位);
    • and p10,p11,#0x0000ffffffffffff : mask 高 16 位抹零,得到 buckets 存入 p10 寄存器, 即去掉 mask,留下 buckets;
    • and p12, p1, p11, LSR #48 :p11(cache) 右移 48 位,得到 mask(即 p11 存储 mask),mask & p1(msgSend 的第二个参数 cmd-sel) ,得到 sel-imp 的下标 index(即搜索下标) 存入p12(cache insert 写入时的哈希下标计算是通过 sel & mask,读取时也需要通过这种方式);
  • 类的结构排布为 isa、superclass、cache_t、class_data_bits ,这里要进行的是查找缓存的流程,缓存的信息是存储在 cache_t 中的。x16 是上一步中获取到的类信息,x16 偏移 16 字节就是取到 cache_t 结构,存入 p11 中。
	#define CACHE            (2 * __SIZEOF_POINTER__)
	ldr p11, [x16, #CACHE]             
  • p11 为 _maskAndBuckets ,它的低 48 位存储 buckets , 高16位存储 mask,#0x0000ffffffffffff 转为二进制如下:
    在这里插入图片描述

  • ② 从 cache 中分别取出 buckets 和 mask,并由 mask 根据哈希算法计算出哈希下标:

	#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
		and	p10, p11, #0x0000ffffffffffff	// p10 = buckets
		and	p12, p1, p11, LSR #48		// x12 = _cmd & mask
	#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
		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
  • 源码分析:
    • 通过 cache 和 0x0000ff ffffffffff 的 & 运算,将高 16 位 mask 抹零,得到 buckets 指针地址,即 p10 = buckets;
    • 将 cache 右移 48 位,得到 mask,即 p11 = mask;
    • 将 objc_msgSend 的参数p1(即第二个参数_cmd)& mask,通过哈希算法得到需要查找存储 sel-imp 的 bucket 下标 index,即 p12 = index = _cmd & mask。在存储 sel-imp 时,也是通过同样哈希算法计算哈希下标进行存储,所以读取也需要通过同样的方式读取;
	static inline mask_t cache_hash(SEL sel, mask_t mask) 
	{
    
    
	    return (mask_t)(uintptr_t)sel & mask;
	}
  • ③ 根据所得的 哈希下标 index 和 buckets 首地址,取出 哈希下标 对应的 bucket;
    • PTRSHIFT 等于 3,左移 4 位(即 24 = 16 字节),目的是计算出 bucket 实际占用的大小,结构体 bucket_t 中 sel 占 8 字节,imp 占 8 字节;
    • 根据计算的 哈希下标 index 乘以 单个 bucket 占用的内存大小 ,得到 buckets 首地址在实际内存中的偏移量;
    • 通过首地址 + 实际偏移量,获取哈希下标 index 对应的 bucket;
	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
		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:	// wrap: p12 = first bucket, w11 = mask
	#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
		add	p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
						// p12 = buckets + (mask << 1+PTRSHIFT)
	#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
		add	p12, p12, p11, LSL #(1+PTRSHIFT)
						// p12 = buckets + (mask << 1+PTRSHIFT)
	#else
	#error Unsupported cache mask storage for ARM64.
	// 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
	
	LLookupEnd$1:
	LLookupRecover$1:
	3:	// double wrap
		JumpMiss $0
	
	.endmacro
  • 源码分析:
    • add p12, p10, p12, LSL #(1+PTRSHIFT) :p12 是下标 p10 是 buckets 数组首地址,下标 * 1<<4(即16) 得到实际内存的偏移量,通过 buckets 的首地址偏移,获取 bucket 存入 p12 寄存器;LSL #(1+PTRSHIFT) 就是得到一个 bucket 占用的内存大小 ,相当于 mask = occupied -1,即 _cmd & mask 取余;
    • ldp p17, p9, [x12] :从x12(即p12)中取出 bucket 分别将 imp 和 sel 存入 p17(存储imp) 和 p9(存储sel);
    • 1: cmp p9, p1 :比较 sel 与 p1(传入的参数cmd);
    • b.ne 2f :如果不相等,
    • CacheHit $0 :如果相等即命中,直接返回 imp;
    • CheckMiss $0 :如果一直找不到 buckets ,则 CheckMiss;
    • cmp p12, p10 :判断 p12(下标对应的 bucket ) 是否等于 p10(buckets数组第一个元素);
    • b.eq 3f :如果等于,获取哈希下标对应的bucket;
    • ldp p17, p9, [x12, #-BUCKET_SIZE]! :从x12(即p12 buckets 首地址)- 实际需要平移的内存大小BUCKET_SIZE,得到得到第二个bucket元素,imp-sel分别存入p17-p9,即向前查找;
    • b 1b :继续对比 sel 与 cmd;
  • ④ 根据获取的 bucket,取出其中的 sel 存入 p17,即 p17 = sel ,取出 imp 存入 p9,即 p9 = imp
  • ⑤ 比较获取的 bucket 中 sel 与 objc_msgSend 的第二个参数的 _cmd(即p1) 是否相等;
    • 如果相等,则直接跳转至 CacheHit ,即 缓存命中 ,返回 imp;
    • 如果不相等,则:如果一直都找不到,直接跳转至 CheckMiss ,因为 $0 是 normal,会跳转至 __objc_msgSend_uncached ,即进入慢速查找流程;如果根据 index 获取的 bucket 等于 buckets 的第一个元素,则将当前 bucket 设置为 buckets 的最后一个元素(通过 buckets 首地址 + mask 右移 44 位(等同于左移 4 位)直接定位到 buckets 的最后一个元素),然后继续向前查找;
    • CacheHit 源码如下:
	.macro CacheHit
	.if $0 == NORMAL
		TailCallCachedImp x17, x12, x1, x16	// authenticate and call imp
	.elseif $0 == GETIMP
		mov	p0, p17
		cbz	p0, 9f			// don't ptrauth a nil imp
		AuthAndResignAsIMP x0, x12, 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, x12, x1, x16	// authenticate imp and re-sign as IMP
		ret				// return imp via x17
	.else
	.abort oops
	.endif
	.endmacro
	
	.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
	
	.macro JumpMiss
	.if $0 == GETIMP
		b	LGetImpMiss
	.elseif $0 == NORMAL
		b	__objc_msgSend_uncached
	.elseif $0 == LOOKUP
		b	__objc_msgLookup_uncached
	.else
	.abort oops
	.endif
	.endmacro
  • ⑥ objc_msgSend 快速查找 赋值变化 流程

在这里插入图片描述

  • ⑦ 重复⑤的操作,与⑤中唯一区别是,如果当前的 bucket 等于 buckets 的第一个元素,则直接跳转至 JumpMiss ,此时的 $0 是 normal ,也是直接跳转至 __objc_msgSend_uncached ,即进入慢速查找流程;
  • ⑧ objc_msgSend 快速查找整体流程

在这里插入图片描述

五、__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
  • 可以看出,执行查找流程的是 _lookUpImpOrForward 。 至此,objc_msgSend() 的 快速查找流程 就结束了,接下来进入的 慢速查找流程 ,也就是通过 isa superclass 的指向,一层一层寻找下去,直到整个 objc_msgSend() 发送完毕。

猜你喜欢

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