一茫经、前言
一個類對象查找方法雪侥,我們都知道是先從緩存列表中去查找郎逃,然后在去方法列表里查找壤玫,這樣就能快速的查找到相關(guān)的imp
,但是當我們沒有查找到相應(yīng)的imp
時豁护,系統(tǒng)又會做一些什么事情呢?帶著這樣的好奇我們開始源碼的探究欲间,我們知道如果一個方法沒有實現(xiàn)楚里,運行時
是會崩潰并且報錯;如下所示:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[LGPerson sayNB]: unrecognized selector sent to class 0x1000022a8'
二猎贴、動態(tài)方法解析
但是當我們在源碼的動態(tài)方法解析
過程做相應(yīng)的事情時:程序就不會報錯班缎,可以繼續(xù)執(zhí)行并且進行我們想要的執(zhí)行結(jié)果;其中核心代碼來自resolveMethod_locked(inst, sel, cls, behavior);
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
1代碼調(diào)試
我們在項目中創(chuàng)建一個對象LGPerson
繼承自 NSObject
,其中包括了幾個簡單的方法她渴;
@interface LGPerson : NSObject
- (void)sayNB;
- (void)sayMaster;
- (void)say666;
- (void)sayHello;
+ (void)sayNB;
+ (void)lgClassMethod;
@end
而實現(xiàn)只包括以下幾個方法
- (void)sayHello{
NSLog(@"%s",__func__);
}
- (void)sayNB{
NSLog(@"%s",__func__);
}
- (void)sayMaster{
NSLog(@"%s",__func__);
}
+ (void)lgClassMethod{
NSLog(@"%s",__func__);
}
也就是說- (void)say666;
和 + (void)sayNB;
只有聲明达址。沒有實現(xiàn)。我們在main.m
文件中進行相關(guān)的方法調(diào)用趁耗,此時必然會出現(xiàn)文章開頭的崩潰現(xiàn)象沉唠,
LGPerson *person = [LGPerson alloc];
[person say666];
帶著問題的探索進入到動態(tài)方法解析的方法探索resolveInstanceMethod
和resolveClassMethod
,看看究竟在動態(tài)解析過程中做了什么事情;
方法中我們只是簡單的打印了一句方法的名稱來到這里对粪;
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%@ 來了",NSStringFromSelector(sel));
return [super resolveInstanceMethod:sel];
}
此時我們看到控制臺仍然會崩潰右冻,但是我們打印的方法日志會出現(xiàn)在崩潰之前装蓬,
這里打印兩次著拭;也就是說這個動態(tài)方法解析的過程中resolveInstanceMethod
會調(diào)用兩次 ;為什么會走兩次接下來會分析牍帚;
根據(jù)條件判斷儡遮,我們看到當前的behavior = 3
,而 LOOKUP_RESOLVER = 2
在根據(jù)計算slowpath(2)
進入我們的第一次動態(tài)方法解析;動態(tài)方法解析的源碼如下暗赶,由于我們是調(diào)用的對象方法鄙币,所以走圖中標注的地方,
動態(tài)方法的實現(xiàn)如下蹂随;
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
// lookup resolveInstanceMethod
if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, sel);
IMP imp = lookUpImpOrNil(inst, sel, cls);
所以第一次動態(tài)方法解析的sel == resolveInstanceMethod
,然后將執(zhí)行后的imp
返回十嘿,再次進行方法查找流程。所以此處會執(zhí)行兩次岳锁。
接著在resolveInstanceMethod
中簡單的進行一個處理如下绩衷;
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%@ 來了",NSStringFromSelector(sel));
if (sel == @selector(say666)) {
NSLog(@"%@ 來了",NSStringFromSelector(sel));
IMP imp = class_getMethodImplementation(self, @selector(sayMaster));
Method sayMMethod = class_getInstanceMethod(self, @selector(sayMaster));
const char *type = method_getTypeEncoding(sayMMethod);
return class_addMethod(self, sel, imp, type);
}
return [super resolveInstanceMethod:sel];
}
此時就會出現(xiàn)一個神奇的現(xiàn)象。程序正常執(zhí)行。這就是動態(tài)方法解析的作用咳燕;
2 原理解析勿决;
動態(tài)方法解析
的源碼截圖如上:分為兩種,一種是對象招盲,另一種是類低缩;
- 1 如果是對象方法,則在當前類對象中查找曹货,因為對象方法就存儲在當前類對象的方法列表中
- 2 如果是類方法咆繁,先從本類中查找是否有該類的
imp
,如果找到就直接返回,如果還是沒有找到顶籽,再對該類的元類
,根元類
么介,NSObject
中查詢,如果有就返回蜕衡,沒有就進行消息轉(zhuǎn)發(fā)
流程 - 3
消息轉(zhuǎn)發(fā)
也分類兩種情況壤短,一種是快速轉(zhuǎn)發(fā)
,一種是慢速轉(zhuǎn)發(fā)
流程慨仿;
三久脯、快速轉(zhuǎn)發(fā)流程
1 日志輔助
上面我們已經(jīng)知道了相關(guān)的消息處理流程,我們接下來分析一下相關(guān)的消息快速轉(zhuǎn)發(fā)流程镰吆,我們借助了instrumentObjcMessageSends
進行相關(guān)的日志跟蹤帘撰;代碼如下;
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
instrumentObjcMessageSends(YES);
[person sayInstanceMethod];
instrumentObjcMessageSends(NO);
NSLog(@"Hello, World!");
}
return 0;
}
再次進入會到相應(yīng)的位置
此時編譯項目我們找到相應(yīng)的日志文件;/tmp/msgSends
目錄下會多出一個文件;
打開文件州既,里邊有相應(yīng)的方法執(zhí)行信息窥淆;
從以上我們能發(fā)現(xiàn)執(zhí)行的流程是resolveInstanceMethod
,再到forwardingTargetForSelector
,繼而到methodSignatureForSelector
,這就是以上的 動態(tài)方法解析
> 消息快速轉(zhuǎn)發(fā)
> 消息慢速轉(zhuǎn)發(fā)
的完整流程。
2 概念的介紹碎连;
以上得知我們先要進行消息的快速轉(zhuǎn)發(fā),也就是forwardingTargetForSelector
我們在開源的代碼中搜索并沒有找到相應(yīng)的定義。借此我們借助蘋果官方文檔進行查詢综苔;
Returns the object to which unrecognized messages should first be directed.
這就是蘋果官方的定義,也就是找到該方法的第一實現(xiàn)者位岔,從而就就結(jié)束了如筛,
所以此時我們
-(id)forwardingTargetForSelector:(SEL)aSelector
{
return [LGStudent alloc];
}
此時在運行代碼就不會出現(xiàn)崩潰的情況了,因為該方法內(nèi)已經(jīng)查詢到我們調(diào)用方法的實現(xiàn) 抒抬,也就是不管是不是我們自己方法接受者實現(xiàn)的杨刨,只要有這個方法的imp
程序就不會崩潰。
四擦剑,慢速轉(zhuǎn)發(fā)流程
如果快速查找流程沒有實現(xiàn)妖胀,那么就進入慢速查找流程的過程可免;那么就進入慢速轉(zhuǎn)發(fā)的流程;
methodSignatureForSelector
,慢速轉(zhuǎn)發(fā)流程搭載forwardInvocation
來使用做粤;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
anInvocation.target = [LGStudent alloc];
[anInvocation invoke];
}
此時我們同樣也能得到相應(yīng)的程序正常執(zhí)行流程浇借,這就是慢速轉(zhuǎn)發(fā)的過程,
通過NSInvocation
我們能看到相關(guān)的定義如下
所以此處我們不僅僅能修改target
還能監(jiān)控其他的屬性怕品;
通過控制臺我們能監(jiān)控相關(guān)的NSInvocation
屬性妇垢,從而動態(tài)的修改相關(guān)內(nèi)容也能起到慢速轉(zhuǎn)發(fā)的作用和目的。
五肉康、總結(jié)
以上就是對象調(diào)用方法的整套消息轉(zhuǎn)發(fā)流程闯估,從而實現(xiàn)消息轉(zhuǎn)發(fā),從創(chuàng)建對象吼和,到查找對象的isa
,在進入類的bits_t
,以及相關(guān)的類的結(jié)構(gòu)涨薪,緩存以及imp
的查找,到最后的方法轉(zhuǎn)發(fā)機制炫乓,這就是一個對象在iOS開發(fā)系統(tǒng)的存在和意義刚夺,通過以上的相關(guān)學(xué)習(xí)和文章的整理,自己也對相關(guān)的消息機制進行一個深刻的認識末捣,這就是成長路上的一點小小的進步吧侠姑,繼續(xù)努力。