iOS底層原理 - 探尋Runtime本質(zhì) 之 消息機(jī)制

面試題引發(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++代碼:

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):

_objc_msgSend函數(shù)

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

CacheLookup宏

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

CheckMiss宏

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

__objc_msgSend_uncached函數(shù)

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

MethodTableLookup宏

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

_class_lookupMethodAndLoadCache3函數(shù)

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

objc_msgSend()執(zhí)行流程

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

lookUpImpOrForward函數(shù)

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

getMethodNoSuper_nolock函數(shù)

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

search_method_list函數(shù)

如果方法列表是有序的,通過(guò)二分查找方法;
否則遍歷列表查找方法。

二分查找實(shí)現(xiàn)原理如下:

findMethodInSortedMethodList函數(shù)

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

方法查找執(zhí)行流程

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


(2) 動(dòng)態(tài)解析階段

lookUpImpOrForward函數(shù)

由以上代碼可知:
如果消息發(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ù)

_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)解析執(zhí)行流程
動(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為:

class_addMethod函數(shù)
// 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ā)階段。

lookUpImpOrForward函數(shù)

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

_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ā)流程圖如下:

消息轉(zhuǎn)發(fā)執(zhí)行流程
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è)階段的具體分析以及流程圖可從上文得知。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容