iOS进阶之方法查找(三)

372 阅读3分钟

更多文章请点击下方:

  1. iOS进阶之Runtime初探(一)
  2. iOS进阶之对象与方法的本质(二)
  3. iOS进阶之动态方法解析(四)
  4. iOS进阶之消息转发(五)

方法查找流程

objc_msgSend发达消息是通过汇编实现的,主要原因有两个:

  1. C语言不能通过函数直接保留未知的参数,跳转到任意的指针,汇编可以通过寄存器保存
  2. 执行速度快

方法查找主要来源有两种方式

  1. 通过汇编快速查找 oc方法都存在类里,类里会从cache_t缓存中找,通过哈希表查找IMP
struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

  1. 慢速

    通过C、C++配合汇编一起完成的,如果通过方法1中的缓存没有找到,就会通过lookup查找,找到后还会存到cache_t缓存中,方便下次查找。

如果方法2没有找到,会经过一个复杂的过程来查找。

方法查找源码分析

`/********************************************************************
 *
 * id objc_msgSend(id self, SEL _cmd, ...);
 * IMP objc_msgLookup(id self, SEL _cmd, ...);
 * 
 * objc_msgLookup ABI:
 * IMP returned in x17
 * x16 reserved for our use but not used
 *
 ********************************************************************/

	.data
	.align 3
	.globl _objc_debug_taggedpointer_classes
_objc_debug_taggedpointer_classes:
	.fill 16, 8, 0
	.globl _objc_debug_taggedpointer_ext_classes
_objc_debug_taggedpointer_ext_classes:
	.fill 256, 8, 0

	ENTRY _objc_msgSend
	UNWIND _objc_msgSend, NoFrame
	MESSENGER_START
	cmp	x0, #0			// nil check and tagged pointer check
	b.le	LNilOrTagged		//  (MSB tagged pointer looks negative)
	ldr	x13, [x0]		// x13 = isa
	and	x16, x13, #ISA_MASK	// x16 = class	

汇编入口 ENTRY _objc_msgSend

  1. 如果指针小于等于LNilOrTagged,直接返回
  2. LGetIsaDone处理完毕(通过isa找到相应的类)
  3. CacheLookup找一般参数:NORMAL
.macro CacheLookup
	// x1 = SEL, x16 = isa
	ldp	x10, x11, [x16, #CACHE]	// x10 = buckets, x11 = occupied|mask
	and	w12, w1, w11		// x12 = _cmd & mask
	add	x12, x10, x12, LSL #4	// x12 = buckets + ((_cmd & mask)<<4)

	ldp	x9, x17, [x12]		// {x9, x17} = *bucket

CacheLookup是一个宏定义

/********************************************************************
 *
 * CacheLookup NORMAL|GETIMP|LOOKUP
 * 
 * Locate the implementation for a selector in a class method cache.
 *
 * Takes:
 *	 x1 = selector
 *	 x16 = class to be searched
 *
 * Kills:
 * 	 x9,x10,x11,x12, x17
 *
 * On exit: (found) calls or returns IMP
 *                  with x16 = class, x17 = IMP
 *          (not found) jumps to LCacheMiss
 *
 ********************************************************************/

#define NORMAL 0
#define GETIMP 1
#define LOOKUP 2

.macro CacheHit
.if $0 == NORMAL
	MESSENGER_END_FAST
	br	x17			// call imp
.elseif $0 == GETIMP
	mov	x0, x17			// return imp
	ret
.elseif $0 == LOOKUP
	ret				// return imp via x17
.else
.abort oops
.endif
.endmacro

.macro CheckMiss
	// miss if bucket->sel == 0
.if $0 == GETIMP
	cbz	x9, LGetImpMiss
.elseif $0 == NORMAL
	cbz	x9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
	cbz	x9, __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

.macro CacheLookup
	// x1 = SEL, x16 = isa
	ldp	x10, x11, [x16, #CACHE]	// x10 = buckets, x11 = occupied|mask
	and	w12, w1, w11		// x12 = _cmd & mask
	add	x12, x10, x12, LSL #4	// x12 = buckets + ((_cmd & mask)<<4)

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

3:	// wrap: x12 = first bucket, w11 = mask
	add	x12, x12, w11, UXTW #4	// x12 = buckets+(mask<<4)

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

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

3:	// double wrap
	JumpMiss $0
	
.endmacro
  • CacheHit
  1. NORMAL MESSAGE_END_FAST 结束快速查找路径
  2. GETIMP 直接返回参数就可以
  3. LOOKUP
  • add
  1. CacheHit
  2. CheckMiss
  3. JumpMiss
  • CheckMiss (找不到)
  1. GETIMP LGetImpMiss
  2. LOOKUP __objc_msgLookup_uncached
  3. NORMAL __objc_msgSend_uncached(没有找到相应的缓存)
    • MethodTableLookup 方法列表查找
      STATIC_ENTRY __objc_msgSend_uncached
      UNWIND __objc_msgSend_uncached, FrameWithNoSaves
      
      // THIS IS NOT A CALLABLE C FUNCTION
      // Out-of-band x16 is the class to search
      	
      MethodTableLookup
      br	x17
      
      END_ENTRY __objc_msgSend_uncached
      
    • __class_lookupMethodAndLoadCache3 汇编查找 注:从此直接跳转至C 函数中继续查找
/***********************************************************************
* _class_lookupMethodAndLoadCache.
* Method lookup for dispatchers ONLY. OTHER CODE SHOULD USE lookUpImp().
* This lookup avoids optimistic cache scan because the dispatcher 
* already tried that.
**********************************************************************/
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}

_class_lookupMethodAndLoadCache3 C函数

  • imp = cache_getImp(cls, sel); CacheLookUp(去汇编里)

  • 核心:retry

    • cache_getImp再次执行 由于Oc动态特性,随时都有可能修改或操作数据,导致出现问题,所以保险起见再取一次
    • getMethodNoSuper_nolock 从自己的方法列表查找,如果找到填充到缓存中
    • curClass = cls->superclass cache_getImp(curClass, sel) 从递归父类去找知道的NSObject
    • 如果还是没有找到 NO ImpleMentaton Found.Try Method reslover once