方法的本質(zhì)
我們通過clang命令 clang -rewrite-objc main.m 將main文件編譯成main.cpp文件
//main函數(shù)中方法的調(diào)用
LGPerson *person = [LGPerson alloc];
[person sayHello];
//clang后的方法的調(diào)用
LGPerson *person = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayHello"));
可以看出 方法的調(diào)用本質(zhì)是 objc_msgSend來發(fā)送消息疤估。我們還可以手動(dòng)調(diào)用來驗(yàn)證一下
導(dǎo)入頭文件<objc/message.h>
LGPerson *person = [LGPerson alloc];
//1.方法的調(diào)用
[person study];
//2.消息發(fā)送 ((void (*)(id, SEL))(void *)objc_msgSend)(p, sel_registerName("study"));
打印結(jié)果如下
可以看到打印結(jié)果是一致的框仔,這也證明了[person study]等價(jià)于objc_msgSend(person, sel_registerName("study"))。
執(zhí)行父類的方法
- (instancetype)init {
if (self = [super init]) {
NSLog(@"%@", [self class]);
NSLog(@"%@", [super class]);
}
return self;
}
先來看一下這段代碼打印的是什么
打印的結(jié)果都是 LGTeacher锐膜,這是為什么呢溪王?
我們用clang命令編譯LGTeacher.m,得到如下編譯代碼
static instancetype _I_ LGTeacher_init(LGTeacher * self, SEL _cmd) {
if (self = ((LGTeacher *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("LGTeacher"))}, sel_registerName("init"))) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_x6_75ftxbbd4t97nl_2t2sh7kww0000gn_T_ LGTeacher_8d6cff_mi_0, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class")));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_x6_75ftxbbd4t97nl_2t2sh7kww0000gn_T_ LGTeacher_8d6cff_mi_1, ((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("LGStudent"))}, sel_registerName("class")));
}
return self;
}
[self class]發(fā)送的消息是objc_msgSend,消息接收者是self,方法編號(hào)是class
[super class]本質(zhì)是objc_msgSendSuper, 消息接收者也是self, 方法編號(hào)class, 所以[super class]打印出來的一樣是self這個(gè)對象所指的類,也就是LGStudent腮鞍。
小結(jié)
- [super class]中的super只是一個(gè)編譯指示器,就是讓當(dāng)前對象獲取該方法莹菱,越過本類移国,直接從父類找。
- 消息的接受這還是本類self
方法快速查找
搜索objc_msgSend 在源碼中以 objc-msg-arm64為例道伟,看objc_msg_arm64中的源碼迹缀,可以看到都是匯編代碼使碾。這里我們可以思考一個(gè)問題,蘋果為什么objc_msgSend這部分代碼要使用匯編來編寫呢祝懂?答案很簡單--效率票摇。匯編的效率是比c/c++更快的,因?yàn)閰R編大多是直接對寄存器的讀寫砚蓬,相比較對內(nèi)存的操作更底層矢门,效率也更高。
//進(jìn)入objc_msgSend流程
ENTRY _objc_msgSend
//流程開始灰蛙,無需frame
UNWIND _objc_msgSend, NoFrame
//判斷p0(消息接受者)是否存在颅和,不存在則重新開始執(zhí)行objc_msgSend
cmp p0, #0 // nil check and tagged pointer check
//如果支持小對象類型。返回小對象或空
#if SUPPORT_TAGGED_POINTERS
//b是進(jìn)行跳轉(zhuǎn)缕允,b.le是小于判斷峡扩,也就是小于的時(shí)候LNilOrTagged
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
//等于,如果不支持小對象障本,就LReturnZero
b.eq LReturnZero
#endif
//通過p13取isa
ldr p13, [x0] // p13 = isa
//通過isa取class并保存到p16寄存器中
GetClassFromIsa_p16 p13, 1, x0 // p16 = class
//LGetIsaDone是一個(gè)入口
LGetIsaDone:
// calls imp or objc_msgSend_uncached
//進(jìn)入到緩存查找或者沒有緩存查找方法的流程
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
// nil check判空處理教届,直接退出
b.eq LReturnZero // nil check
GetTaggedClass
b LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif
LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
ret
END_ENTRY _objc_msgSend
objc_msgSend 開始到找類對像cache方法結(jié)束的流程。
首先判斷receiver是否存在驾霜,以及是否是taggedPointer類型的指針案训,如果不是我們?nèi)〕鰧ο蟮膇sa指針(x13寄存器中),通過isa指針找到類對象(x16寄存器)粪糙,然后通過CacheLookup强霎,在類對象的cache中查找是否有方法緩存,如果有就調(diào)用蓉冈,如果沒有走objc_msg_uncached分支城舞。
方法慢速查找(從方法列表中查找)
方法在cache內(nèi)找不到就是調(diào)用_objc_msgSend_uncached函數(shù),我們在_objc_msgSend_uncached函數(shù)內(nèi)部可以看到它會(huì)調(diào)用MethodTableLookup函數(shù)寞酿,MethodTableLookup函數(shù)會(huì)調(diào)用_lookUpImpOrForward家夺。_lookUpImpOrForward函數(shù)在匯編內(nèi)是看不到,那我們直接在源碼內(nèi)搜索lookUpImpOrForward伐弹。
lookUpImpOrForward函數(shù)內(nèi)部首先會(huì)判斷cache內(nèi)部有沒有方法拉馋,因?yàn)樵诙嗑€程環(huán)境下現(xiàn)在cache可能有該方法。
如果本類沒有的話會(huì)調(diào)用getMethodNoSuper_nolock函數(shù)惨好,然后依次調(diào)用search_method_list_inline煌茴、findMethodInSortedMethodList函數(shù)。我們可以看到findMethodInSortedMethodList是采用二分查找來獲取方法的日川。
當(dāng)我們在本類里面找到方法時(shí)蔓腐,它會(huì)進(jìn)行g(shù)oto done,然后會(huì)進(jìn)入log_and_fill_cache函數(shù)逗鸣,可以看到log_and_fill_cache函數(shù)內(nèi)部調(diào)用了cache.insert()方法合住,也就是加入緩存里面了绰精。需要注意的是哪個(gè)類調(diào)用就會(huì)加入哪個(gè)類的cache里面撒璧,也就是如果子類調(diào)用父類的方法透葛,之后該方法會(huì)緩存到子類的cache。
如果本類也沒有方法卿樱,通過curClass = curClass->getSuperclass()僚害,把curClass轉(zhuǎn)換成父類,然后找父類的cache繁调,cache如果沒有在通過二分查找找方法列表萨蚕。如果再?zèng)]有把curClass轉(zhuǎn)換成父類的父類依次查找。
總結(jié):
消息的慢速查找流程:
- 調(diào)用
lookUpImpOrForward
- 看本類的
cache
里面有沒有該方法 - 如果沒有看本類的
methodList
有沒有該方法(二分查找) - 如果沒有看父類的
cache
里面有沒有該方法 - 如果沒有看父類的
methodList
有沒有該方法(二分查找) - 然后逐級(jí)向上查找
- 當(dāng)父類是
nil
的時(shí)候也就是查找到NSObject
的時(shí)候蹄胰,都沒有的話就會(huì)進(jìn)入消息轉(zhuǎn)發(fā)流程岳遥。