我們知道將源代碼轉(zhuǎn)化為可執(zhí)行的文件要經(jīng)過三個(gè)階段:編譯瞪慧、鏈接髓考、運(yùn)行。不同的編譯語言有有所不同弃酌。
在iOS中函數(shù)的調(diào)用氨菇,實(shí)質(zhì)就是給對象發(fā)消息。而在程序的運(yùn)行過程中妓湘,函數(shù)調(diào)用的實(shí)現(xiàn)是不確定的查蓉,只有在運(yùn)行時(shí)才去確定函數(shù)的實(shí)現(xiàn)。在程序運(yùn)行時(shí)榜贴,編譯器會把函數(shù)的調(diào)用轉(zhuǎn)換成objc_msgsend
豌研。這個(gè)函數(shù)會動態(tài)的尋找下一個(gè)要執(zhí)行的方法。
-
編譯階段:
[receiver selector]
方法調(diào)用被編譯為:
-
objc_msgSend(receiver, selector)
(不帶參數(shù)方法)唬党; -
objc_msgSend(receiver, selector, org1, org2)
(帶參數(shù)方法)鹃共;
-
-
運(yùn)行時(shí)階段:
- 通過
receiver
(消息接受者 )的isa指針,找到receiver
所屬的Class
(類)驶拱; - 在
receiver
所屬類的method list(方法列表)
中找對應(yīng)的selector
(先找方法緩存列表再找方法列表); - 如果在
Class
中沒有找到selector
對應(yīng)的實(shí)現(xiàn)霜浴,就繼續(xù)去superClass(父類)
方法列表中查找; - 如果找到對應(yīng)
selector
,直接執(zhí)行receiver
中selector
方法對應(yīng)的實(shí)現(xiàn)(IMP)蓝纲; - 若找不到對應(yīng)的
selector
,消息將被轉(zhuǎn)發(fā)或者臨時(shí)想receiver
添加這個(gè)selector
對應(yīng)的實(shí)現(xiàn)阴孟,否則會crash。
- 通過
- 函數(shù)的調(diào)用過程圖如下:
尋找方法的實(shí)現(xiàn)過程大致如下:
消息轉(zhuǎn)發(fā)
如上圖中在繼承關(guān)系中找不到方法實(shí)現(xiàn)時(shí)驻龟,程序就會crash温眉。但是在crash之前我們還可以重寫以下四個(gè)方法進(jìn)行處理:
///當(dāng)調(diào)用一個(gè)不存在的類方法時(shí)調(diào)用
+ (BOOL)resolveClassMethod:(SEL)sel;
///當(dāng)調(diào)用一個(gè)不存在的實(shí)例方法時(shí)調(diào)用
+ (BOOL)resolveInstanceMethod:(SEL)sel;
///將這個(gè)不存在的方法重定向到其他類進(jìn)行處理,返回一個(gè)類的實(shí)例
- (id)forwardingTargetForSelector:(SEL)aSelector;
///將這個(gè)不存在的方法打包成NSInvocation丟進(jìn)來翁狐,需要調(diào)用invokeWithTarget:給某個(gè)能執(zhí)行方法的實(shí)例
- (void)forwardInvocation:(NSInvocation *)anInvocation;
復(fù)制代碼
以實(shí)例方法為例來說一下這幾個(gè)方法的調(diào)用流程:
首先調(diào)用
+ (BOOL)resolveClassMethod:(SEL)sel
如果返回NO,就調(diào)用
- (id)forwardingTargetForSelector:(SEL)aSelector
-
如果返回nil凌蔬,就調(diào)用
- (void)forwardInvocation:(NSInvocation *)anInvocation
具體流程圖如下:
我們看一下下面的未實(shí)現(xiàn)的點(diǎn)擊方法調(diào)用情況:
[button addTarget:self action:@selector(tapAction) forControlEvents:UIControlEventTouchUpInside];
void addMethod(id self, SEL _cmd) {
NSLog(@"addMethod complete");
}
///當(dāng)調(diào)用一個(gè)不存在的類方法時(shí)調(diào)用
+ (BOOL)resolveClassMethod:(SEL)sel {
NSLog(@"resolveClassMethod complete");
return YES;
}
///當(dāng)調(diào)用一個(gè)不存在的實(shí)例方法時(shí)調(diào)用
+ (BOOL)resolveInstanceMethod:(SEL)sel {
class_addMethod(self, sel, (IMP)addMethod, "v@:*");
NSLog(@"resolveInstanceMethod complete");
return YES;
}
///將這個(gè)不存在的方法重定向到其他類進(jìn)行處理露懒,返回一個(gè)類的實(shí)例
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"forwardingTargetForSelector complete");
return nil;
}
///將這個(gè)不存在的方法打包成NSInvocation丟進(jìn)來,需要調(diào)用invokeWithTarget:給某個(gè)能執(zhí)行方法的實(shí)例
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"forwardInvocation complete");
}
復(fù)制代碼
打印數(shù)據(jù):
2019-07-19 16:16:48.429089+0800 ThinTableVIew1[37672:364932] resolveInstanceMethod complete
2019-07-19 16:16:48.429242+0800 ThinTableVIew1[37672:364932] addMethod complete
復(fù)制代碼
從上面的打印信息我們可以知道tapAction
方法調(diào)用到resolveInstanceMethod
就停止了砂心,因?yàn)槲覀兘o系統(tǒng)添加了一個(gè)方法懈词,并返回了YES。這時(shí)候就不會再調(diào)用兩個(gè)forward方法了辩诞。 簡單說來坎弯,這四個(gè)方法都是用來添加未處理方法的。區(qū)別在于,resolveInstanceMethod是在本類中添加方法抠忘,并告訴系統(tǒng)該方法是否執(zhí)行撩炊;forwardingTargetForSelector 是自己處理不了,轉(zhuǎn)給其它實(shí)例做處理崎脉;如果經(jīng)過以上幾步還是處理不了拧咳,那么就走到了forwardInvocation中,系統(tǒng)會把這個(gè)方法的所有信息都打包給我們囚灼,做最后的處理骆膝。