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流程分析圖

相關文章
相關標籤/搜索