objc_msgSend 流程探索

首先来温习一下缓存

Runtime运行时

运行时:顾名思义就是代码跑起来,被装载到内存中去了。markdown

Runtime有两个版本:函数

  1. Legacy版本(早期版本),对应的变成接口:Object-C 1.0
  2. Modern版本(现行版本) 对应的变成接口:Object-C 2.0

早期版本用于Object-C 1.0 32位的Mac OS X的平台上,现行版本iPhone程序和Mac OS X10.5及之后系统的64位程序。oop

Runtime层级:ui

来一段举例代码spa

#import <objc/message.h>

@interface CFFahter : NSObject

- (void)sayHello;

- (void)sayHow;

@end

@implementation CFFahter

- (void)sayHello{
    NSLog(@"sayHello");
}

- (void)sayHow{
    NSLog(@"sayHow");
}

@end

@interface CFSon : CFFahter

- (void)sayHello;

- (void)sayHow;

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {

       CFSon *son = [[CFSon alloc]init];

       objc_msgSend(son,sel_registerName("sayHow"));

       [son sayHow];

       /*
        CFSon *son = ((CFSon *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("CFSon"), sel_registerName("alloc"));        ((void (*)(id, SEL))(void *)objc_msgSend)((id)son, sel_registerName("sayHow"));        ((void (*)(id, SEL))(void *)objc_msgSend)((id)son, sel_registerName("sayHello"));        */

    }
    return NSApplicationMain(argc, argv);
}
复制代码

编译后底层代码实现rest

CFSon *son = ((CFSon *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("CFSon"),  sel_registerName("alloc"));
 ((void (*)(id, SEL))(void *)objc_msgSend)((id)son, sel_registerName("sayHow")); ((void (*)(id, SEL))(void *)objc_msgSend)((id)son, sel_registerName("sayHello"));
复制代码

咱们发现,在上层的@selector()对应底层sel_registerName,还能够等价于NSSeletorFromString(),code

接下来咱们试着换一种方式来调用sayHow方法orm

objc_msgSend(son,sel_registerName(@"sayHow"));
复制代码

此刻发现会报错多了一个参数,缘由是当前objc_msgSend 函数默认参数只有一个,只要把其检查机制关掉便可对象

打印结果:

接下来再添加一个继承于CFFahter的子类CFSon类,为父类发送消息

@interface CFSon : CFFahter

- (void)sayHello;

- (void)sayHow;

@end

@implementation CFSon

- (void)sayHello{
    NSLog(@"sayHow");
}

- (void)sayHow{
    NSLog(@"sayHow");
}

@end
复制代码

此时须要另外一个向父类发送消息的API

//objc_msgSendSuper(<#struct objc_super * _Nonnull super#>, <#SEL  _Nonnull op, ...#>)
  struct objc_super cfsuper;
  cfsuper.receiver = son;
  cfsuper.super_class = [CFFahter class];
  objc_msgSendSuper(&cfsuper, sel_registerName("sayHello"));
复制代码

sayHow方法一样能打印

在objc_msgSendSuper方法中,第一个参数是一个objc_super的结构体,结构体里有一个接收消息的receiver和一个当前父类super_class两个变量。

小结:Runtime三种方法实现

  • 方法 objc_msgSend
  • c函数名
  • OC方法——消息(SEL IMP) ,SEL——>IMP->内容

objc_msgSend底层探索

消息接受者流程: 

对象 - ISA - 方法(类) - cache_t - methodlist

#endif

	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://获取isa完毕后
	// calls imp or objc_msgSend_uncached
	CacheLookup NORMAL, _objc_msgSend //开始从缓存里面imp流程

//GetClassFromIsa_p16 宏:
.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:
复制代码

CacheLookup内部实现

.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]//CACHE宏定义:	(2* _ _SIZEOF_POINTER)		
	// 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

	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)查询sel是否相同
	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 //mask = copu - 1 = 4 - 1 = 3,
#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.
#endif

	// 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
复制代码

大概过程咱们可用经过一段伪代码来简单梳理一下

//获取当前的对象
id son = 0x1000

//获取isa
isa_t isa = 0x000000

//isa -> class(类) -> cache
cache_t cache - isa + 16字节

//mask/bucjets 在arm64环境下
buckets = cache & 0x000ffffffffff

//获取mask
mask = cache LSR #48

//下标 = mask & sel
index = mask &s1

//从buckets遍历得到单个bucket
bucket = buckets + index * 16 //(sel imp = 16)

int count = 0;

do {
    if(count == 2){ goto CheckMiss,//出口

    }

    //接下来循环
    if(bucket.sel == _cmd){
        //判断bucket里面的sel 是否匹配_cmd,直接返回imp
        if(bucket == buckets){
            //第二层判断,bucket = 第一个元素
            //bucket人为设定的最后一个元素
            bucket = buckets + mask * 16
            count ++;
        }

        //向前查找的顺序进行缓存的查找
        bucket --;
        imp = bucket.imp;
        sel = bucket.sel;
}while (bucket.sel != _cmd)

return imp

CheckMiss:
    CheckMiss(normal)
复制代码

objc_msgSend流程分析图

相关文章
相关标签/搜索