面試題引發(fā)的思考:
Q: 簡(jiǎn)述消息轉(zhuǎn)發(fā)機(jī)制?
- 消息發(fā)送階段:負(fù)責(zé)從 類(lèi)及父類(lèi) 的 緩存列表及方法列表 查找方法;
- 動(dòng)態(tài)解析階段:如果消息發(fā)送階段沒(méi)有找到方法,則會(huì)進(jìn)入動(dòng)態(tài)解析階段,負(fù)責(zé) 動(dòng)態(tài)的添加 方法實(shí)現(xiàn);
- 消息轉(zhuǎn)發(fā)階段:如果也沒(méi)有實(shí)現(xiàn)動(dòng)態(tài)解析方法,則會(huì)進(jìn)行消息轉(zhuǎn)發(fā)階段,將消息 轉(zhuǎn)發(fā) 給可以處理消息的 接收者 來(lái)處理;
- 報(bào)錯(cuò):如果也沒(méi)有實(shí)現(xiàn)消息轉(zhuǎn)發(fā)方法,會(huì)報(bào)錯(cuò)
unrecognzied selector sent to instance。
1. 方法調(diào)用的本質(zhì)
前兩章分別對(duì)isa結(jié)構(gòu)的本質(zhì)、Class結(jié)構(gòu)的本質(zhì)做了探究,下面探究方法調(diào)用的本質(zhì)。
// TODO: ----------------- main -----------------
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
[person test];
}
return 0;
}
轉(zhuǎn)成C++代碼:

由以上代碼可知:
OC的方法調(diào)用都是轉(zhuǎn)化為objc_msgSend()函數(shù),即消息機(jī)制,通過(guò)Runtime給方法調(diào)用者發(fā)送消息。
而objc_msgSend(person, @selector(test));的作用是給消息接收者person發(fā)送test消息。
那么接下來(lái)我們探究objc_msgSend()函數(shù)的調(diào)用過(guò)程:
objc_msgSend()的執(zhí)行流程可以分為三個(gè)階段:
- 消息發(fā)送階段:負(fù)責(zé)從類(lèi)及父類(lèi)的緩存列表及方法列表查找方法;
- 動(dòng)態(tài)解析階段:如果消息發(fā)送階段沒(méi)有找到方法,則會(huì)進(jìn)入動(dòng)態(tài)解析階段,負(fù)責(zé)動(dòng)態(tài)的添加方法實(shí)現(xiàn);
- 消息轉(zhuǎn)發(fā)階段:如果也沒(méi)有實(shí)現(xiàn)動(dòng)態(tài)解析方法,則會(huì)進(jìn)行消息轉(zhuǎn)發(fā)階段,將消息轉(zhuǎn)發(fā)給可以處理消息的接收者來(lái)處理;
- 報(bào)錯(cuò):如果也沒(méi)有實(shí)現(xiàn)消息轉(zhuǎn)發(fā)方法,會(huì)報(bào)錯(cuò)
unrecognzied selector sent to instance。
接下來(lái)通過(guò)源碼探尋以上三個(gè)階段的實(shí)現(xiàn)。
(1) 消息發(fā)送階段
OC源碼中搜索_objc_msgSend,在objc-msg-arm64.s匯編文件找到_objc_msgSend函數(shù)的實(shí)現(xiàn):

判斷消息接收者是否為nil:
如果為nil,直接退出程序;
否則繼續(xù)執(zhí)行代碼至CacheLookup NORMAL,去緩存查找:

判斷緩存中找到對(duì)應(yīng)的方法:
如果可以找到,執(zhí)行CacheHit $0,進(jìn)而調(diào)用方法;
否則執(zhí)行CheckMiss $0:

如果緩存中沒(méi)有找到對(duì)應(yīng)方法,執(zhí)行__objc_msgSend_uncached函數(shù):

執(zhí)行MethodTableLookup即方法列表查找:

內(nèi)部核心代碼為__class_lookupMethodAndLoadCache3函數(shù):
匯編的函數(shù)比C++的多一個(gè)下劃線,所以查找_class_lookupMethodAndLoadCache3即可:

以上步驟進(jìn)流程圖如下:

下面對(duì)lookUpImpOrForward函數(shù)進(jìn)行分析:

通過(guò)getMethodNoSuper_nolock函數(shù)在類(lèi)的方法列表查找方法:

通過(guò)getMethodNoSuper_nolock函數(shù)遍歷得到類(lèi)對(duì)象的方法列表,然后通過(guò)search_method_list函數(shù)查找方法:

如果方法列表是有序的,通過(guò)二分查找方法;
否則遍歷列表查找方法。
二分查找實(shí)現(xiàn)原理如下:

以上步驟進(jìn)流程圖如下:

如果消息發(fā)送階段沒(méi)有找到方法,就會(huì)進(jìn)入動(dòng)態(tài)解析階段。
(2) 動(dòng)態(tài)解析階段

由以上代碼可知:
如果消息發(fā)送階段沒(méi)有找到方法,就會(huì)進(jìn)入動(dòng)態(tài)解析階段;
動(dòng)態(tài)解析方法之后,triedResolver = YES;,然后goto retry,那么會(huì)重新查找一遍方法,并跳過(guò)動(dòng)態(tài)解析階段。
動(dòng)態(tài)解析階段主要函數(shù)為_class_resolveMethod:

_class_resolveMethod函數(shù)會(huì)根據(jù)類(lèi)對(duì)象或者元類(lèi)對(duì)象,分別調(diào)用resolveInstanceMethod方法或resolveClassMethod方法。
動(dòng)態(tài)解析進(jìn)流程圖如下:

動(dòng)態(tài)解析過(guò)程實(shí)例
a> 實(shí)例方法動(dòng)態(tài)解析
// TODO: ----------------- Person類(lèi) -----------------
@interface Person : NSObject
- (void)test;
@end
@implementation Person
@end
// TODO: ----------------- main -----------------
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
[person test];
}
return 0;
}
以上代碼運(yùn)行報(bào)錯(cuò):unrecognized selector sent to instance 0x1006088b0,因?yàn)樵?code>Person類(lèi)中聲明了test方法,卻并沒(méi)有實(shí)現(xiàn)。
接下來(lái)通過(guò)動(dòng)態(tài)解析解決問(wèn)題,相關(guān)API為:

// TODO: ----------------- Person類(lèi) -----------------
@interface Person : NSObject
- (void)test;
@end
@implementation Person
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(test)) {
// 獲取其他方法
Method otherMethod = class_getInstanceMethod(self, @selector(other));
// 動(dòng)態(tài)添加方法
class_addMethod(self, sel, method_getImplementation(otherMethod), method_getTypeEncoding(otherMethod));
// 返回YES表示有動(dòng)態(tài)添加方法
return YES;
}
return [super resolveInstanceMethod:sel];
};
- (void)other {
NSLog(@"%s", __func__);
}
@end
// 打印結(jié)果
Demo[1234:567890] -[Person other]
由打印結(jié)果可知:
person在調(diào)用test方法時(shí)經(jīng)過(guò)動(dòng)態(tài)解析成功調(diào)用了other方法。
b> 類(lèi)方法動(dòng)態(tài)解析
// TODO: ----------------- Person類(lèi) -----------------
@interface Person : NSObject
+ (void)classTest;
@end
@implementation Person
+ (BOOL)resolveClassMethod:(SEL)sel {
if (sel == @selector(classTest)) {
// 獲取其他方法
Method otherMethod = class_getClassMethod(self, @selector(classOther));
// 動(dòng)態(tài)添加方法
class_addMethod(object_getClass(self), sel, method_getImplementation(otherMethod), method_getTypeEncoding(otherMethod));
// 返回YES表示有動(dòng)態(tài)添加方法
return YES;
}
return [super resolveClassMethod:sel];
}
+ (void)classOther {
NSLog(@"%s", __func__);
}
@end
// TODO: ----------------- main -----------------
int main(int argc, const char * argv[]) {
@autoreleasepool {
[Person classTest];
}
return 0;
}
(3) 消息轉(zhuǎn)發(fā)階段
如果消息發(fā)送階段沒(méi)有找到方法,動(dòng)態(tài)解析階段沒(méi)有對(duì)方法進(jìn)行動(dòng)態(tài)解析,接下來(lái)就會(huì)進(jìn)入消息轉(zhuǎn)發(fā)階段。

消息轉(zhuǎn)發(fā)階段,會(huì)調(diào)用_objc_msgForward_impcache函數(shù):

匯編中找到__objc_msgForward_impcache函數(shù)實(shí)現(xiàn),該函數(shù)調(diào)用__objc_msgForward函數(shù);
__objc_msgForward函數(shù)實(shí)現(xiàn)調(diào)用__objc_forward_handler函數(shù);
無(wú)法找到__objc_forward_handler函數(shù)實(shí)現(xiàn),查找資料了解到消息轉(zhuǎn)發(fā)機(jī)制是不開(kāi)源的。
消息轉(zhuǎn)發(fā)過(guò)程實(shí)例
a> 實(shí)例方法消息轉(zhuǎn)發(fā)
// TODO: ----------------- Person類(lèi) -----------------
@interface Person : NSObject
- (void)test;
@end
@implementation Person
@end
// TODO: ----------------- main -----------------
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
[person test];
}
return 0;
}
由以上代碼可知:
Person類(lèi)中只聲明test方法,不實(shí)現(xiàn)方法,此時(shí)調(diào)用test方法會(huì)崩潰。
在Student類(lèi)中實(shí)現(xiàn)一個(gè)test方法,在Person類(lèi)中通過(guò)forwardingTargetForSelector:方法將消息轉(zhuǎn)發(fā)對(duì)象設(shè)置為Student對(duì)象:
// TODO: ----------------- Person類(lèi) -----------------
@interface Person : NSObject
- (void)test;
@end
@implementation Person
/**
消息轉(zhuǎn)發(fā)
@param aSelector 方法
@return 返回能夠處理消息的對(duì)象
*/
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
return [[Student alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
@end
// TODO: ----------------- Student類(lèi) -----------------
@interface Student : NSObject
@end
@implementation Student
- (void)test {
NSLog(@"%s", __func__);
}
@end
// TODO: ----------------- main -----------------
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
[person test];
}
return 0;
}
// 打印結(jié)果
// Runtime[939:123812] -[Student test]
如果forwardingTargetForSelector函數(shù)返回為nil或者沒(méi)有實(shí)現(xiàn)的話,就會(huì)調(diào)用methodSignatureForSelector函數(shù),用來(lái)返回一個(gè)方法簽名;
然后調(diào)用forwardInvocation函數(shù),其內(nèi)部參數(shù)為NSInvocation類(lèi)型,NSInvocation類(lèi)型封裝一個(gè)方法的調(diào)用,包括方法調(diào)用者、方法、方法的參數(shù);
然后在forwardInvocation函數(shù)內(nèi)修改方法調(diào)用對(duì)象。
// TODO: ----------------- Person類(lèi) -----------------
@interface Person : NSObject
- (void)test;
@end
@implementation Person
/**
消息轉(zhuǎn)發(fā)
@param aSelector 方法
@return 返回能夠處理消息的對(duì)象
*/
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
// 返回nil則會(huì)調(diào)用methodSignatureForSelector方法
return nil;
}
return [super forwardingTargetForSelector:aSelector];
}
/**
為另一個(gè)類(lèi)實(shí)現(xiàn)的消息創(chuàng)建一個(gè)有效的方法簽名
@param aSelector 方法
@return 方法簽名:返回值類(lèi)型、參數(shù)類(lèi)型
*/
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
return [[[Student alloc] init] methodSignatureForSelector:aSelector];
}
return [super methodSignatureForSelector:aSelector];
}
/**
將選擇器轉(zhuǎn)發(fā)給一個(gè)真正實(shí)現(xiàn)了該消息的對(duì)象
@param anInvocation 封裝了一個(gè)方法調(diào)用,包括:方法調(diào)用者,方法,方法的參數(shù)
@param anInvocation.target 方法調(diào)用者
@param anInvocation.selector 方法
@param [anInvocation getArgument: NULL atIndex: 0]; 方法的參數(shù)
*/
- (void)forwardInvocation:(NSInvocation *)anInvocation {
// 此時(shí)anInvocation.target 還是person對(duì)象,需要修改target為可以執(zhí)行方法的方法調(diào)用者。
[anInvocation invokeWithTarget:[[Student alloc] init]];
}
@end
// 打印結(jié)果
// Runtime[1016:190175] -[Student test]
消息轉(zhuǎn)發(fā)流程圖如下:

b> 類(lèi)方法消息轉(zhuǎn)發(fā)
// TODO: ----------------- Person類(lèi) -----------------
@interface Person : NSObject
+ (void)test;
@end
@implementation Person
+ (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
return [Student class];
}
return [super forwardingTargetForSelector:aSelector];
}
@end
// TODO: ----------------- Student類(lèi) -----------------
@interface Student : NSObject
@end
@implementation Student
+ (void)test {
NSLog(@"%s", __func__);
}
@end
// TODO: ----------------- main -----------------
int main(int argc, const char * argv[]) {
@autoreleasepool {
[Person test];
}
return 0;
}
// 打印結(jié)果
// Runtime[1245:167577] +[Student test]
如果forwardingTargetForSelector函數(shù)返回為nil或者沒(méi)有實(shí)現(xiàn)的話:
// TODO: ----------------- Person類(lèi) -----------------
@interface Person : NSObject
- (void)test;
@end
@implementation Person
+ (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
return nil;
}
return [super forwardingTargetForSelector:aSelector];
}
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
return [[Student class] methodSignatureForSelector:aSelector];
}
return [super methodSignatureForSelector:aSelector];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation {
[anInvocation invokeWithTarget:[Student class]];
}
@end
// 打印結(jié)果
// Runtime[1335:187977] +[Student test]
(4) 總結(jié)
- OC的方法調(diào)用都是轉(zhuǎn)化為
objc_msgSend()函數(shù),即消息機(jī)制,通過(guò)Runtime給方法調(diào)用者發(fā)送消息。- 方法調(diào)用分為三個(gè)階段:
消息發(fā)送、動(dòng)態(tài)方法解析、消息轉(zhuǎn)發(fā)。
以上三個(gè)階段的具體分析以及流程圖可從上文得知。