Objective-C 是一個動態(tài)語言,在動態(tài)中創(chuàng)建類和對象婴削、進行消息傳遞和轉(zhuǎn)發(fā)嘁酿。想要更好的理解 Objective-C 那就離不開 Runtime(運行時) 隙券。
什么是Runtime?
Runtime是用C、C++闹司、匯編編寫的一套為OC提供運行時功能的api
初見objc_msgSend
創(chuàng)建一個Student的類
int main(int argc, const char * argv[]) {
@autoreleasepool {
Student *student = [[Student alloc] init];
[student study];
}
return 0;
}
在終端使用clang命令將main.m編譯成main.cpp
clang -rewrite-objc main.m
此時我們會發(fā)現(xiàn)main.m文件的下方會多出一個main.cpp的文件,打開并移動到最下方我們看到如下一段代碼:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
Student *student = ((Student *(*)(id, SEL))(void *)objc_msgSend)((id)((Student *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Student"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)student, sel_registerName("study"));
}
return 0;
}
我們發(fā)現(xiàn)在student創(chuàng)建和調(diào)用study的方法是,都調(diào)用了objc_msgSend,那么objc_msgSend到底是什么?其作用又是什么呢?
通過匯編初探objc_msgSend
通過打斷點,查看匯編我們發(fā)現(xiàn)objc_msgSend的實現(xiàn)是在libobjc動態(tài)鏈接庫中(objc4源碼)娱仔。在libobjc源碼庫中全局搜索objc_msgSend,發(fā)現(xiàn)它的實現(xiàn)是用匯編寫的游桩。
下面我們就以arm64系統(tǒng)下的匯編就行分析,全部搜索objc_msgSend找到objc-msg-arm64.s文件,找到ENTRY _objc_msgSend,方法的進入是從ENTRY開始的:
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
cmp p0, #0 //arm64下有31位寄存器; 對比0號寄存器是否為空, 如果為空則代表當前接受著沒有,如果為空則返回nil
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
// [x0]是消息接收者
//如果我們要對student發(fā)送消息,我們需要用到用到對象方法時我們就可以通過isa找到類Student,如果是類方法是我們則需要isa來找到元類
ldr p13, [x0] // p13 = isa
//通過isa尋找類
GetClassFromIsa_p16 p13 // p16 = class
LGetIsaDone://查找isa完畢
CacheLookup NORMAL//讀取方法 // calls imp or objc_msgSend_uncached
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
b.eq LReturnZero // nil check
GetClassFromIsa_p16
.macro GetClassFromIsa_p16 /* src */
#if SUPPORT_INDEXED_ISA
// Indexed isa
mov p16, $0 // optimistically set dst = src
//判斷當前是否為non-pointer isa
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
.macro CacheLookup
// p1 = SEL, p16 = isa
ldp p10, p11, [x16, #CACHE]//在cache_t一文中我們提到了要拿到方法緩存需要首地址平移16個字節(jié) // p10 = buckets, p11 = occupied|mask
#if !__LP64__//w11用w是因為mask是32位的,不需要用64位的
and w11, w11, 0xffff // p11 = mask
#endif
/**
static inline mask_t cache_hash(cache_key_t key, mask_t mask)
{
return (mask_t)(key & mask);
}
*/
and w12, w1, w11 // x12 = _cmd & mask
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 //沒找到就繼續(xù)找2流程 // 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//找到了,再去走3流程,是為了存一份緩存,方便下一次填充緩存
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
3: // wrap: p12 = first bucket, w11 = mask
add p12, p12, w11, UXTW #(1+PTRSHIFT)
// p12 = buckets + (mask << 1+PTRSHIFT)
// 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
3: // double wrap
JumpMiss $0
.endmacro
如果緩存沒有命中的話會走CheckMiss方法
//CacheLookup NORMAL|GETIMP|LOOKUP
.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
NORMAL 模式下走__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
有上面代碼可知,接下來會進入MethodTableLookup流程:
MethodTableLookup
//方法存在bits->rw->ro的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)]
// receiver and selector already in x0 and x1
mov x2, x16
bl __class_lookupMethodAndLoadCache3
// 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
由上面匯編可知,其最終要查找的是__class_lookupMethodAndLoadCache3方法,當我們在匯編中并搜索不到該方法的實現(xiàn),通過前面x0..x8, q0..q7的準備工作我們猜想,其可能是為調(diào)用C或者C++的方法做準備,當我們?nèi)炙阉鱛class_lookupMethodAndLoadCache3時發(fā)現(xiàn)在objc-rutime-new.mm文件中找到了該方法的實現(xiàn)
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
這個函數(shù)開始就是我們熟知的C/C++的代碼了牲迫,我們終于也不用再看匯編部分的代碼了,消息發(fā)送從快速查找過渡到了慢速查找流程。
總結(jié):
在編譯期調(diào)用 objc_msgSend 函數(shù) , 在匯編代碼執(zhí)行緩存查找 sel 對應的 imp , 找到就會返回調(diào)用 , 找不到則由快速查找過渡到了慢速查找流程借卧。
拓展isKindOfClass和isMemberOfClass
int main(int argc, const char * argv[]) {
@autoreleasepool {
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; //
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; //
BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]]; //
BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]]; //
NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]]; //
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]]; //
BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]]; //
BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]]; //
NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
}
return 0;
}
+ (BOOL)isKindOfClass:(Class)cls;
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
由self獲取到元類,如果相等就返回YES,不相等就繼續(xù)找元類的父類也就是NSObject, [NSObject class]的類明顯就是NSObject因此[(id)[NSObject class] isKindOfClass:[NSObject class]]返回值為1,同理[(id)[LGPerson class] isKindOfClass:[LGPerson class]]的類為LGPerson,無論是元類還是元類的父類都不可能為LGPerson,因此此處返回為0;
+ (BOOL)isMemberOfClass:(Class)cls;
+ (BOOL)isMemberOfClass:(Class)cls {
//就是元類和類做對比,如果相等返回1,不想等返回0
return object_getClass((id)self) == cls;
}
元類和類肯定是不一樣的,因此[(id)[NSObject class] isMemberOfClass:[NSObject class]]和[(id)[LGPerson class] isMemberOfClass:[LGPerson class]]返回值皆為0;
- (BOOL)isKindOfClass:(Class)cls;
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
第一步都是對比類,例如: [(id)[NSObject alloc] isKindOfClass:[NSObject class]]第一步就是對比該類是不是NSObject類,如果是直接返回1,明顯[NSObject alloc]就是NSObject類,所以此時返回為1,同理 [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]] 返回值也為1;
- (BOOL)isMemberOfClass:(Class)cls;
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
對比cls和[self class]是都相同,和isKindOfClass區(qū)別在于, isKindOfClass如果第一步不同的時候還會往父類去查找,容錯更高一些,明顯[(id)[NSObject alloc] isMemberOfClass:[NSObject class]]和[(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]]返回值皆為1;
至此我們可以知道答應的結(jié)果應該是:
2020-01-27 23:03:06.936314+0800 Test[8438:1379585] re1 :1
re2 :0
re3 :0
re4 :0
2020-01-27 23:03:06.938168+0800 Test[8438:1379585] re5 :1
re6 :1
re7 :1
re8 :1