首先來溫習一下緩存
運行時:顧名思義就是代碼跑起來,被裝載到內存中去了。markdown
Runtime有兩個版本:函數
早期版本用於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兩個變量。
消息接受者流程:
對象 - 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流程分析圖