前言
如果在動(dòng)態(tài)解析階段不做任何處理的話,我們調(diào)用一個(gè)未實(shí)現(xiàn)的方法會(huì)crash,下面來分析一下进宝,crash之前系統(tǒng)還做了什么?
一枷恕、探索消息轉(zhuǎn)發(fā)
1. instrumentObjcMessageSends打開log開關(guān)
#import <Foundation/Foundation.h>
#import "DZStudent.h"
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
DZStudent *student = [DZStudent alloc] ;
instrumentObjcMessageSends(true);
[student saySomething];
instrumentObjcMessageSends(false);
}
return 0;
}
extern void instrumentObjcMessageSends(BOOL flag)
:是蘋果的私有API党晋,我們可以控制log開關(guān),打印日志信息徐块。
日志文件位置:/tmp/msgSend-xxx
2. 查看日志文件
我們可以發(fā)現(xiàn)未玻,方法在調(diào)用報(bào)失敗doesNotRecognizeSelector
之前的調(diào)用順序:resolveInstanceMethod -> forwardingTargetForSelector -> methodSignatureForSelector -> doesNotRecognizeSelector。
resolveInstanceMethod
是動(dòng)態(tài)方法決議胡控,我們上一文已經(jīng)做了分析扳剿,本文只針對(duì)后邊的方法進(jìn)行源碼分析。
二铜犬、快速轉(zhuǎn)發(fā)
1. forwardingTargetForSelector分析
我們?nèi)炙阉骱笪柚眨l(fā)現(xiàn)這個(gè)方法是NSObject
中實(shí)現(xiàn)的方法轻庆,只做了返回nil
的操作:
+ (id)forwardingTargetForSelector:(SEL)sel {
return nil;
}
我們可以結(jié)合方法介紹或者官方文檔來進(jìn)行分析:
2. 方法說明(看discussion):
- 該方法的目的就是不能處理方法的時(shí)候癣猾,交給另外一個(gè)對(duì)象來執(zhí)行敛劝,但是不能返回
self
,否則會(huì)一直找不到陷入死循環(huán)纷宇。- 該方法效率很高夸盟,如果不實(shí)現(xiàn)或者返回
nil
,會(huì)走到相對(duì)效率低的forwardInvocation:
方法進(jìn)行處理像捶。- 所以我們稱
forwardingTargetForSelector
為快速轉(zhuǎn)發(fā)上陕,forwardInvocation
為慢速轉(zhuǎn)發(fā)。- 被轉(zhuǎn)發(fā)的消息接收者拓春,參數(shù)和返回值等需要和原方法相同释簿。
3. 方法使用
當(dāng)訪問
DZStudent
未實(shí)現(xiàn)的saySomething
方法時(shí),可以使用- (id)forwardingTargetForSelector:(SEL)aSelector
進(jìn)行方法轉(zhuǎn)發(fā)硼莽,用DZTeacher
這個(gè)實(shí)現(xiàn)saySomething
方法的對(duì)象來接收庶溶,具體實(shí)現(xiàn)代碼如下:
main.m
DZStudent *student = [DZStudent alloc];
instrumentObjcMessageSends(true);
[student saySomething];
instrumentObjcMessageSends(false);
DZStudent.m
// 消息轉(zhuǎn)發(fā)流程
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s",__func__);
if (aSelector == @selector(saySomething)) {
return [DZTeacher alloc];
}
return [super forwardingTargetForSelector:aSelector];
}
DZTeacher.m
@implementation DZTeacher
- (void)saySomething{
NSLog(@"%s",__func__);
}
@end
打印結(jié)果
// 失敗打印
2020-06-17 15:58:28.646428+0800 008-方法查找-消息轉(zhuǎn)發(fā)[17172:5671635] -[DZStudent saySomething]: unrecognized selector sent to instance 0x101805980
2020-06-17 15:58:28.658327+0800 008-方法查找-消息轉(zhuǎn)發(fā)[17172:5671635] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[DZStudent saySomething]: unrecognized selector sent to instance 0x101805980'
// 成功打印
2020-06-17 14:58:14.506074+0800 008-方法查找-消息轉(zhuǎn)發(fā)[10077:5556271] -[DZStudent forwardingTargetForSelector:] -- saySomething
2020-06-17 14:58:14.507704+0800 008-方法查找-消息轉(zhuǎn)發(fā)[10077:5556271] -[DZTeacher saySomething]
通俗點(diǎn)講,這個(gè)方法的作用就是懂鸵,自己的活自己干不了偏螺,就交給能干活的人去干。
三匆光、慢速轉(zhuǎn)發(fā)
當(dāng)快速轉(zhuǎn)發(fā)流程也沒有實(shí)現(xiàn)套像,或者返回nil
,就進(jìn)入慢速轉(zhuǎn)發(fā)流程终息。
1. methodSignatureForSelector
同樣在源碼中全局搜索之后夺巩,我們發(fā)現(xiàn)這個(gè)方法也是NSObject
中實(shí)現(xiàn)的方法:
// Replaced by CF (returns an NSMethodSignature)
+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)sel {
_objc_fatal("+[NSObject instanceMethodSignatureForSelector:] "
"not available without CoreFoundation");
}
// Replaced by CF (returns an NSMethodSignature)
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
_objc_fatal("+[NSObject methodSignatureForSelector:] "
"not available without CoreFoundation");
}
// Replaced by CF (returns an NSMethodSignature)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
_objc_fatal("-[NSObject methodSignatureForSelector:] "
"not available without CoreFoundation");
}
官方文檔:
方法說明:
該方法用于協(xié)議的實(shí)現(xiàn),如果有對(duì)象未能直接實(shí)現(xiàn)的消息周崭,則重寫此方法返回適當(dāng)?shù)?strong>方法簽名劲够。然后將簽名對(duì)象作為參數(shù)傳給
forwardInvocation
方法,在forwardInvocation
里邊將消息給能處理該消息的對(duì)象休傍,避免最后調(diào)用didNotRecognizeSelector
方法導(dǎo)致崩潰征绎。
下來我們繼續(xù)了解 forwardInvocation 方法:
2. forwardInvocation
同樣是在源碼中全局搜索之后,我們發(fā)現(xiàn)這個(gè)方法也是NSObject中實(shí)現(xiàn)的:
+ (void)forwardInvocation:(NSInvocation *)invocation {
[self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)];
}
官方文檔:
forwardInvocation
和methodSignatureForSelector
必須是同時(shí)重寫磨取。并且該方法可以自由指派多個(gè)對(duì)象接受該消息人柿。
3. doesNotRecognizeSelector
// Replaced by CF (throws an NSException)
+ (void)doesNotRecognizeSelector:(SEL)sel {
_objc_fatal("+[%s %s]: unrecognized selector sent to instance %p",
class_getName(self), sel_getName(sel), self);
}
// Replaced by CF (throws an NSException)
- (void)doesNotRecognizeSelector:(SEL)sel {
_objc_fatal("-[%s %s]: unrecognized selector sent to instance %p",
object_getClassName(self), sel_getName(sel), self);
}
我們可以發(fā)現(xiàn),最終是doesNotRecognizeSelector
方法拋出的異常忙厌,所以我們可以重寫forwardInvocation
方法凫岖,這樣不執(zhí)行父類的方法,程序就不會(huì)崩潰了逢净。
4. 方法使用
在forwardInvocation
方法中哥放,我們可以把這個(gè)方法看成是一個(gè)未知方法收集箱歼指,在這里可以隨意選擇你可以處理的方法,進(jìn)行歸類集中處理甥雕。
main.m
#import <Foundation/Foundation.h>
#import "DZStudent.h"
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
DZStudent *student = [DZStudent alloc];
instrumentObjcMessageSends(true);
[student saySomething];
instrumentObjcMessageSends(false);
}
return 0;
}
DZTeacher.m
@implementation DZTeacher
- (void)saySomething{
NSLog(@"%s",__func__);
}
@end
DZStudent.m
備注:關(guān)于方法簽名串"v@:"可以參考官方文檔:方法簽名Type Encodings
// 返回一個(gè)方法簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(saySomething)) { // v @ :
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s ",__func__);
SEL aSelector = [anInvocation selector];
if ([[DZTeacher alloc] respondsToSelector:aSelector])
[anInvocation invokeWithTarget:[DZTeacher alloc]];
else
[super forwardInvocation:anInvocation];
}
// 打印
2020-06-17 17:17:08.148187+0800 008-方法查找-消息轉(zhuǎn)發(fā)[24033:5801203] -[DZStudent methodSignatureForSelector:] -- saySomething
2020-06-17 17:17:08.149424+0800 008-方法查找-消息轉(zhuǎn)發(fā)[24033:5801203] -[DZStudent forwardInvocation:]
當(dāng)然此處轉(zhuǎn)發(fā)方法也可以什么都不做處理踩身,也僅僅是轉(zhuǎn)發(fā)不出去而已,并不會(huì)崩潰社露。
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s ",__func__);
}
// 打印
2020-06-17 18:24:21.692113+0800 008-方法查找-消息轉(zhuǎn)發(fā)[32243:5925309] -[DZStudent methodSignatureForSelector:] -- saySomething
2020-06-17 18:24:21.699464+0800 008-方法查找-消息轉(zhuǎn)發(fā)[32243:5925309] -[DZStudent forwardInvocation:]
四挟阻、總結(jié)
- 當(dāng)動(dòng)態(tài)方法決議也沒有做處理時(shí),就會(huì)進(jìn)入快速轉(zhuǎn)發(fā)(
forwardingTargetForSelector
)階段峭弟。 - 如果快速轉(zhuǎn)發(fā)也沒有做處理附鸽,會(huì)繼續(xù)到慢速轉(zhuǎn)發(fā)(
forwardInvocation
)階段。 - 即使
forwardInvocation
中不實(shí)現(xiàn)后續(xù)方法也不會(huì)崩潰瞒瘸。 -
forwardInvocation
和forwardingTargetForSelector
類似坷备,都可以將A類的方法轉(zhuǎn)發(fā)的B類的實(shí)現(xiàn)中去,但是forwardInvocation
的優(yōu)點(diǎn)是更加靈活情臭,forwardingTargetForSelector
只能轉(zhuǎn)發(fā)發(fā)到固定的一個(gè)對(duì)象省撑。而forwardInvocation
可以轉(zhuǎn)發(fā)的多個(gè)對(duì)象中去,甚至不做處理谎柄,也僅僅是轉(zhuǎn)發(fā)不出去而已丁侄,并不會(huì)崩潰。
消息的流轉(zhuǎn)及對(duì)應(yīng)的作用如下所示:
流轉(zhuǎn):
消息發(fā)送 -> 消息查找 -> 動(dòng)態(tài)方法決議 -> 快速轉(zhuǎn)發(fā) -> 慢速轉(zhuǎn)發(fā)
作用:
像某個(gè)對(duì)象或類發(fā)送消息 -> 自己有沒有處理 -> 自己有沒有特殊處理(動(dòng)態(tài)方法決議) -> 指定的人有沒有處理 -> 愛誰處理誰處理