上一篇文章(OC底層方法的本質、查找流程)主要說了方法的查找流程,可是方法最後找到了NSObject都沒有對應的方法實現,就直接崩潰嗎?固然不是的,蘋果還給咱們提供一個消息轉發的機制,下面我們來具體看一下。面試
做爲一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個個人iOS交流羣:812157648,無論你是小白仍是大牛歡迎入駐 ,分享BAT,阿里面試題、面試經驗,討論技術, 你們一塊兒交流學習成長!markdown
在上一篇文章介紹的查找IMP的方法中,當找不到的時候,會執行一次_class_resolveMethod方法,具體實現以下(詳細解釋見註釋):app
/***********************************************************************
* _class_resolveMethod
* Call +resolveClassMethod or +resolveInstanceMethod.
* Returns nothing; any result would be potentially out-of-date already.
* Does not check if the method already exists.
**********************************************************************/
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
/**
判斷當前傳入的類是不是元類。
若是是元類,則走類方法的動態決議。
若是不是元類,即普通的類,則走實例方法的動態決議。
*/
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
// 實例方法動態決議
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
// 類方法的動態決議
_class_resolveClassMethod(cls, sel, inst);
/**
此處的if判斷可謂重點。
通過上面_class_resolveClassMethod方法後,有可能尚未找到一個方法的IMP。
此時判斷一下,是否有能夠實現的類方法的IMP。
若是有,該if方法不進。
若是沒有,則再走一遍_class_resolveInstanceMethod方法,調用NSObject的resolveInstanceMethod方法,爲何這麼作呢,由於根據以前文章介紹的isa走位圖可知,根元類是繼承NSObject的,同時擁有NSObject全部的方法,因此再給一次調用resolveInstanceMethod機會,來處理這個類方法。
*/
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
複製代碼
上面的方法則是一個統一的調用入口,下面分別看一下。oop
實例方法動態決議方法以下:學習
/***********************************************************************
* _class_resolveInstanceMethod
* Call +resolveInstanceMethod, looking for a method to be added to class cls.
* cls may be a metaclass or a non-meta class.
* Does not check if the method already exists.
**********************************************************************/
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
/**
此判斷會去查找當前類以及父類等是否實現了resolveInstanceMethod方法。
若是沒有實現,則直接return,由於if下面的邏輯便是向該方法發送消息,蘋果不可能讓本身的程序崩潰的。
其實NSObject類已經實現了這個方法,具體見下面,那麼老祖宗都實現了,這個判斷是否多餘呢?若是你寫的類不繼承NSObject呢,是否是就沒有默認實現了呢。
若是有實現,則繼續走下面的邏輯。
*/
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
// 建立消息,給類發送resolveInstanceMethod消息,此時本類的resolveInstanceMethod方法會被調用,若是沒實現,那麼調用父類的該方法。
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
複製代碼
下面看一下NSObject類的resolveInstanceMethod方法實現:spa
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return NO;
}
複製代碼
那麼當咱們實現的這個方法被調用了,均可以作什麼呢?來看下面一段代碼:.net
@interface GYMPerson : NSObject
- (void)playGame;
- (void)sleep;
@end
@implementation GYMPerson
- (void)sleep {
NSLog(@"%s 調用了", __func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return NO;
}
@end
複製代碼
上面有一個GYMPerson類,有兩個實例方法,其中playGame只定義,沒有具體實現, sleep既定義又實現,而後我們開始調用playGame方法。code
int main(int argc, const char * argv[]) {
@autoreleasepool {
GYMPerson *person = [GYMPerson alloc];
[person playGame];
}
return 0;
}
複製代碼
當調用playGame方法後,程序直接掛掉了,報錯以下:orm
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[GYMPerson playGame]: unrecognized selector sent to instance 0x101843700'
複製代碼
緣由在於咱們沒有playGame方法的具體實現,雖然類中複寫了resolveInstanceMethod方法,可是在方法裏沒有作任何操做。對象
下面將resolveInstanceMethod方法裏面加點代碼:
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(playGame)) {
Method method = class_getInstanceMethod(self, @selector(sleep));
const char *types = method_getTypeEncoding(method);
IMP imp = class_getMethodImplementation(self, @selector(sleep));
bool isAdded = class_addMethod(self, sel, imp, types);
return isAdded;
}
return [super resolveInstanceMethod:sel];
}
複製代碼
而後再運行程序,便會獲得:
GYMDemo[21368:7583751] -[GYMPerson sleep] 調用了
複製代碼
此時sleep方法調用了,程序沒有崩潰,由於在resolveInstanceMethod方法裏面,咱們將sleep方法的IMP綁定給了playGame方法,因此執行playGame方法,就是執行sleep方法。
以上就是實例方法的動態決議,下面再看一下類方法的動態決議。
類方法動態決議方法以下:
/***********************************************************************
* _class_resolveClassMethod
* Call +resolveClassMethod, looking for a method to be added to class cls.
* cls should be a metaclass.
* Does not check if the method already exists.
**********************************************************************/
//cls: 元類, sel: 方法, inst: 類
static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
assert(cls->isMetaClass());
/**
判斷元類中是否實現了resolveClassMethod方法。
若是元類沒有實現,找根元類,根元類沒有,則找NSObject。
*/
if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
// 建立消息,給類發送resolveClassMethod消息,此時本類的resolveClassMethod方法會被調用,若是沒實現,那麼調用父類的該方法。
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(_class_getNonMetaClass(cls, inst),
SEL_resolveClassMethod, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveClassMethod adds to self->ISA() a.k.a. cls
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
複製代碼
下面看一下NSObject類的resolveClassMethod方法實現:
+ (BOOL)resolveClassMethod:(SEL)sel {
return NO;
}
複製代碼
那麼當咱們實現的這個方法被調用了,均可以作什麼呢?來看下面一段代碼:
@interface GYMPerson : NSObject
+ (void)loveGirl;
+ (void)loveLife;
@end
@implementation GYMPerson
+ (void)loveLife {
NSLog(@"%s 調用了", __func__);
}
+ (BOOL)resolveClassMethod:(SEL)sel {
return [super resolveClassMethod:sel];
}
@end
複製代碼
上面有一個GYMPerson類,有兩個類方法,其中loveGirl只定義,沒有具體實現, loveLife既定義又實現,而後我們開始調用loveGirl方法。
int main(int argc, const char * argv[]) {
@autoreleasepool {
[GYMPerson loveGirl];
}
return 0;
}
複製代碼
當調用loveGirl方法後,程序直接掛掉了,報錯以下:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[GYMPerson loveGirl]: unrecognized selector sent to class 0x100002750'
複製代碼
緣由在於咱們沒有loveGirl方法的具體實現,雖然類中複寫了resolveClassMethod方法,可是在方法裏沒有作任何操做。
下面將resolveClassMethod方法裏面加點代碼:
+ (BOOL)resolveClassMethod:(SEL)sel {
if (sel == @selector(loveGirl)) {
Method method = class_getClassMethod(objc_getMetaClass("GYMPerson"), @selector(loveLife));
const char *types = method_getTypeEncoding(method);
IMP imp = class_getMethodImplementation(objc_getMetaClass("GYMPerson"), @selector(loveLife));
bool isAdded = class_addMethod(objc_getMetaClass("GYMPerson"), sel, imp, types);
return isAdded;
}
return [super resolveClassMethod:sel];
}
複製代碼
而後再運行程序,便會獲得:
GYMDemo[16988:9071636] +[GYMPerson loveLife] 調用了
複製代碼
此時loveLife方法調用了,程序沒有崩潰,由於在resolveClassMethod方法裏面,咱們將loveLife方法的IMP綁定給了loveGirl方法,因此執行loveGirl方法,就是執行loveLife方法。
若是咱們在代碼裏面沒有實現對應的動態決議方法,那麼程序在崩潰以前,蘋果還給咱們提供了一次轉發的機會,分爲快速轉發和慢速轉發,具體以下。
所謂的快速轉發流程就是指定給別人作。
實例方法快速轉發以下:
- (id)forwardingTargetForSelector:(SEL)sel {
return nil;
}
複製代碼
這個方法返回一個對象類型,意思就是當前對象解決不了的話,那麼可能別的類型的對象有,能解決。 好比有這麼一個類GYMAthlete:
@interface GYMAthlete : NSObject
- (void)playGame;
@end
@implementation GYMAthlete
- (void)playGame {
NSLog(@"%s", __func__);
}運行程序[person playGame];後沒有崩潰,而是調用了GYMAthlete類的playGame方法。
@end
複製代碼
GYMPerson類裏面沒有實現playGame方法,可是GYMAthlete類裏面卻有同名的方法,且實現了。
此時咱們修改forwardingTargetForSelector方法:
- (id)forwardingTargetForSelector:(SEL)sel {
if (sel == @selector(playGame)) {
return [GYMAthlete alloc];
}
return nil;
}
複製代碼
運行程序[person playGame];後沒有崩潰,而是調用了GYMAthlete類的playGame方法。
+ (id)forwardingTargetForSelector:(SEL)sel {
if (sel == @selector(loveGirl)) {
return [GYMAthlete class];
}
return nil;
}
複製代碼
運行程序[GYMPerson loveGirl];後沒有崩潰,而是調用了GYMAthlete類的loveGirl方法。
GYMDemo[18597:9145003] +[GYMAthlete loveGirl]
複製代碼
以上就是快速轉發流程,本身作不了的事情指定給別人作。
慢速轉發流程則是我把方法拋出去,誰能解決誰就幫我解決了,沒人解決也不會崩潰了,由於我已經扔出去了。 與快速轉發不一樣的是,慢速轉發不指定誰去處理,而是扔出去,那麼仍出去的是什麼呢? 方法簽名 下面看一下簽名的方法:
// 實例方法簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return nil;
}
// 類方法方法簽名
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return nil;
}
複製代碼
只要一個簽名方法仍是沒用呢,它還有一個好搭檔,以下:
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"%s", __func__);
}
+ (void)forwardInvocation:(NSInvocation *)invocation {
NSLog(@"%s", __func__);
}
複製代碼
只要一個簽名方法仍是沒用呢,它還有一個好搭檔,以下:
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"%s", __func__);
}
+ (void)forwardInvocation:(NSInvocation *)invocation {
NSLog(@"%s", __func__);
}
複製代碼
能夠通俗的說,先把方法簽名,而後扔到invocation裏面等待處理,有人處理則好,沒人處理也不會崩潰。
下面試驗一下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
GYMPerson *person = [GYMPerson alloc];
[person playGame]; // 沒有具體實現
[GYMPerson loveGirl]; // 沒有具體實現
}
return 0;
}
// 運行playGame方法時,先進入到這裏簽名。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
if (sel == @selector(playGame)) {
NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@:"];
return signature;
}
return nil;
}
// 運行loveGirl方法時,先進入到這裏簽名。
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
if (sel == @selector(loveGirl)) {
NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@:"];
return signature;
}
return nil;
}
// playGame簽名後,將進入forwardInvocation這個方法等待處理。
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL sel = [anInvocation selector];
if (sel == @selector(playGame)) {
// 這裏分別判斷了GYMAthlete和GYMDeveloper的實例對象能不能處理,誰能處理交給誰處理。
if ([[GYMAthlete alloc] respondsToSelector:sel]) {
[anInvocation invokeWithTarget:[GYMAthlete alloc]];
}else if ([[GYMDeveloper alloc] respondsToSelector:sel]) {
[anInvocation invokeWithTarget:[GYMDeveloper alloc]];
}
}
}
// loveGirl簽名後,將進入forwardInvocation這個方法等待處理。
+ (void)forwardInvocation:(NSInvocation *)invocation {
SEL sel = [invocation selector];
if (sel == @selector(loveGirl)) {
// 這裏分別判斷了GYMAthlete和GYMDeveloper類能不能處理,誰能處理交給誰處理。
if ([[GYMAthlete class] respondsToSelector:sel]) {
[invocation invokeWithTarget:[GYMAthlete class]];
}else if ([[GYMDeveloper class] respondsToSelector:sel]) {
[invocation invokeWithTarget:[GYMDeveloper class]];
}
}
}
複製代碼
結果以下:
2020-10-30 22:27:53.729149+0800 GYMDemo[74259:10203752] -[GYMAthlete playGame]
2020-10-30 22:27:53.729812+0800 GYMDemo[74259:10203752] +[GYMAthlete loveGirl]
複製代碼
本篇文章主要講了消息的轉發機制,當消息查找失敗時就會進入轉發階段,轉發分爲三個階段:
第一階段: 動態決議_class_resolveMethod
,指定實現某個方法。
第二階段: forwardingTargetForSelector
: 指定某個對象或者類去實現同名的方法。
第三階段: methodSignatureForSelector
forwardInvocation
組合階段:將方法簽名扔出去,而後再forwardInvocation
方法中,誰想處理就處理。
以上三個階段是逐一順序實現的,若是某個階段實現了,那麼後續階段再也不調用了。
以上內容出自blog.csdn.net/guoyongming…的博客,轉載請註明來源。
原文做者:Daniel_Coder