iOS runtime 消息機(jī)制

  • objc_msgSend

OC中的實(shí)例對(duì)象調(diào)用一個(gè)方法稱作消息傳遞,例如:

    Ink *inkInstance = [[Ink alloc] init];
    [inkInstance sendMessage:@"i like beautiful girl"];

代碼中,我們將inkInstance這個(gè)實(shí)例對(duì)象稱之為消息接收者,sendMessage:我們稱之為選擇子,即所謂的selector,selector參數(shù)共同構(gòu)成消息,所以
[inkInstance sendMessage:@"i like beautiful girl"]
可以理解為將消息:"發(fā)送一個(gè)消息: i like beautiful girl"發(fā)送給消息的接受者inkInstance。
OC中里的消息傳遞采用動(dòng)態(tài)綁定機(jī)制來(lái)決定具體調(diào)用哪個(gè)方法,OC的實(shí)例方法在通過(guò)runtime轉(zhuǎn)寫為C語(yǔ)言后實(shí)際就是一個(gè)函數(shù)(也就是說(shuō)runtime實(shí)際上就是將面向?qū)ο蟮念愞D(zhuǎn)變?yōu)槊嫦蜻^(guò)程的結(jié)構(gòu)體),但是OC并不是在編譯期決定調(diào)用哪個(gè)函數(shù),而是在運(yùn)行期決定,因?yàn)榫幾g期根本不能確定最終會(huì)調(diào)用哪個(gè)函數(shù),這是由于運(yùn)行期可以修改方法的實(shí)現(xiàn)。例如:

id  p= @1024;
//輸出1024
NSLog(@"%@", p);
//程序崩潰,報(bào)錯(cuò)[__NSCFNumber appendString:]: unrecognized selector ...
[p appendString:@"I'm a good boy"];

這段代碼在編譯期沒(méi)有任何問(wèn)題,因?yàn)閕d類型可以指向任何類型的實(shí)例對(duì)象,NSString有一個(gè)方法appendString:,在編譯期不確定這個(gè)p到底具體指代什么類型的實(shí)例對(duì)象,并且在運(yùn)行期還可以給NSNumber類型添加新的方法,因此編譯期發(fā)現(xiàn)有appendString:的函數(shù)聲明就不會(huì)報(bào)錯(cuò),但在運(yùn)行時(shí)找不到在NSNumber類中找不到appendString:方法,就會(huì)報(bào)錯(cuò)。這也就是消息傳遞的強(qiáng)大之處和弊端,編譯期無(wú)法檢查到未定義的方法,運(yùn)行期可以添加新的方法。
而OC則是通過(guò)強(qiáng)大的runtime將這些方法轉(zhuǎn)換為C語(yǔ)言的函數(shù),但是是如何調(diào)用這些函數(shù)的呢,這里就將說(shuō)道我們所說(shuō)的objc_msgSend。(注:objc/msgSend 只有對(duì)象才能發(fā)送消息,因此以objc開頭 導(dǎo)入 #import <objc/message.h> 或者直接導(dǎo)入 #import <objc/runtime.h> 注意 Xcode 6 之后代碼檢查 單獨(dú)使用<objc/message.h>會(huì)報(bào)錯(cuò) builtSeting 修改 Enable Strict Checking of objc_msgSend Calls -> NO 才能調(diào)用 objc_msgSend)

    Ink *inkInstance = [Ink alloc];
    inkInstance = [inkInstance init];
    //為方便查看通過(guò)objc_msgSend轉(zhuǎn)寫后的的代碼,這里用作兩步來(lái)執(zhí)行alloc init方法
    [inkInstance sendMessage:@"i like beautiful girl"];

通過(guò)objc_msgSend即可轉(zhuǎn)譯為

Ink *inkInstance = objc_msgSend(objc_getClass("Ink"),sel_registerName("alloc"));

這行代碼達(dá)到了如下幾個(gè)效果,第一獲取Ink類,第二注冊(cè)alloc方法,第三發(fā)送消息,將消息alloc發(fā)送給類對(duì)象,可以簡(jiǎn)單的將注冊(cè)方法理解為,通過(guò)方法名獲取到轉(zhuǎn)寫后C語(yǔ)言函數(shù)的函數(shù)指針。

inkInstance = objc_msgSend(inkInstance,sel_registerName("init"));

這一行則是,注冊(cè)了init方法,然后通過(guò)objc_msgSend函數(shù)將消息init發(fā)送給消息的接受者inkInstance。

objc_msgSend(inkInstance,sel_registerName("sendMessage:"),@"i like beautiful girl");

這一行代碼同樣是先注冊(cè)方法sendMessage:然后通過(guò)objc_msgSend函數(shù)將消息sendMessage:發(fā)送給消息的接收者,只是多了一個(gè)參數(shù)的傳遞。
到這里,我們應(yīng)該就可以看出OC的runtime通過(guò)objc_msgSend函數(shù)將一個(gè)面向?qū)ο蟮南鬟f轉(zhuǎn)為了面向過(guò)程的函數(shù)調(diào)用。objc_msgSend函數(shù)根據(jù)消息的接受者和selector選擇適當(dāng)?shù)姆椒▉?lái)調(diào)用。這里就涉及到OC的runtime是如何將面向?qū)ο蟮念愑成錇槊嫦蜻^(guò)程的結(jié)構(gòu)體的,我們可以看一下幾個(gè)重要結(jié)構(gòu)體的定義:

文件objc/runtime.h中有如下定義:
struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

    Class super_class                                        
    const char *name                                         
    long version                                             
    long info                                                
    long instance_size                                       
    struct objc_ivar_list *ivars                             
    struct objc_method_list **methodLists                    
    struct objc_cache *cache                                 
    struct objc_protocol_list *protocols                     
}
/* Use `Class` instead of `struct objc_class *` */

文件objc/objc.h文件中有如下定義
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

/// Represents an instance of a class.
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;

注意結(jié)構(gòu)體struct objc_class中包含一個(gè)成員變量struct objc_method_list **methodLists,通過(guò)名稱我們分析出這個(gè)成員變量保存了實(shí)例方法列表。
結(jié)構(gòu)體struct objc_method_list里面包含以下幾個(gè)成員變量:結(jié)構(gòu)體struct _objc_method的大小、方法個(gè)數(shù)以及最重要的方法列表,方法列表存儲(chǔ)的是方法描述結(jié)構(gòu)體struct _objc_method,該結(jié)構(gòu)體里保存了選擇子、方法類型以及方法的具體實(shí)現(xiàn)??梢钥闯龇椒ǖ木唧w實(shí)現(xiàn)就是一個(gè)函數(shù)指針,也就是我們自定義的實(shí)例方法,選擇子也就是selector可以理解為是一個(gè)字符串類型的名稱,用于查找對(duì)應(yīng)的函數(shù)實(shí)現(xiàn)
。
這樣就能解釋objc_msgSend的工作原理的:

  • 首先,通過(guò)接收者的isa指針找到它的class ;

  • 在class的struct objc_method_list找對(duì)應(yīng)的方法 ;

  • 如果class中沒(méi)有找到,繼續(xù)往它的superclass中找 ;

  • 一旦找到對(duì)應(yīng)的這個(gè)函數(shù),就去執(zhí)行它的實(shí)現(xiàn)IMP.
    如果到了繼承樹的根部(通常為NSObject)還沒(méi)有找到,那就會(huì)調(diào)用NSObjec的一個(gè)方法doesNotRecognizeSelector:,這個(gè)方法就會(huì)報(bào)unrecognized selector錯(cuò)誤
    而為了避免每次搜索和靜態(tài)綁定那樣直接跳轉(zhuǎn)到函數(shù)指針指向的位置去執(zhí)行,類對(duì)象也就是結(jié)構(gòu)體struct objc_class中有一個(gè)成員變量struct objc_cache,這個(gè)緩存里緩存的正是搜索方法的匹配結(jié)果,這樣在第二次及以后再訪問(wèn)時(shí)就可以采用映射的方式找到相關(guān)實(shí)現(xiàn)的具體位置。

  • 動(dòng)態(tài)綁定(所屬類動(dòng)態(tài)方法解析)

如果沿繼承樹沒(méi)有搜索到相關(guān)方法則會(huì)向接收者所屬的類進(jìn)行一次請(qǐng)求,看是否能夠動(dòng)態(tài)的添加一個(gè)方法,注意這是一個(gè)類方法,因?yàn)槭窍蚪邮照咚鶎俚念愡M(jìn)行請(qǐng)求。這里就涉及到兩個(gè)方法:

//通過(guò)類對(duì)象調(diào)用的未實(shí)現(xiàn)的方法則會(huì)執(zhí)行此方法
+ (BOOL)resolveClassMethod:(SEL)sel
//通過(guò)實(shí)例對(duì)象調(diào)用的未實(shí)現(xiàn)的方法則會(huì)執(zhí)行此方法
+ (BOOL)resolveInstanceMethod:(SEL)sel

舉個(gè)??
調(diào)用代碼

    //Ink類中即沒(méi)有聲明也沒(méi)有實(shí)現(xiàn)receiveSomething和classMethod這兩個(gè)方法
    Ink *inkInstance = [[Ink alloc]init];
    //調(diào)用實(shí)例方法
    [inkInstance performSelector:@selector(receiveSomething:) withObject:@"調(diào)用了實(shí)例方法 receiveSomething"];
    //調(diào)用了類方法
    [Ink performSelector:@selector(classMethod:) withObject:@"調(diào)用了類方法 classMethod"];

處理代碼

#import "Ink.h"
#import<objc/runtime.h>
@implementation Ink
void messageSend (id self,SEL _cmd,id object) {
    if(strcmp(sel_getName(_cmd), "receiveSomething:") == 0) {
        NSLog(@"message for appendString:%@",object);
    }else if (strcmp(sel_getName(_cmd), "classMethod:") == 0) {
        NSLog(@"message for classMethod:%@",object);
    }
}
#pragma mark 第一步驟(所屬類動(dòng)態(tài)方法解析)
//實(shí)例調(diào)用方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(receiveSomething:)) {
        class_addMethod(self, sel, (IMP)messageSend, "v@:*");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
//類方法
+ (BOOL)resolveClassMethod:(SEL)sel {
    if (sel == @selector(classMethod:)) {
        class_addMethod(object_getClass([self class]), sel, (IMP)messageSend, "v@:*");
        return YES;
    }
    return [super resolveClassMethod:sel];
}

由于Ink類沒(méi)有聲明和定義receiveSomething:classMethod:方法,所以運(yùn)行時(shí)應(yīng)該會(huì)報(bào)unrecognized selector錯(cuò)誤,但是并沒(méi)有。
對(duì)于receiveSomething:
我們重寫了類方法+ (BOOL)resolveInstanceMethod:(SEL)sel,當(dāng)找不到相關(guān)實(shí)例方法的時(shí)候就會(huì)調(diào)用該類方法去詢問(wèn)是否可以動(dòng)態(tài)添加。
對(duì)于classMethod:
我們重寫了類方法+ (BOOL)resolveClassMethod:(SEL)sel,當(dāng)找不到相關(guān)實(shí)例方法的時(shí)候就會(huì)調(diào)用該類方法去詢問(wèn)是否可以動(dòng)態(tài)添加。
如果返回True就會(huì)再次執(zhí)行相關(guān)方法,接下來(lái)看一下如何給一個(gè)類動(dòng)態(tài)添加一個(gè)方法,那就是調(diào)用runtime庫(kù)中的class_addMethod方法,該方法的原型是

BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types);

通過(guò)參數(shù)名可以看出第一個(gè)參數(shù)是需要添加方法的類,第二個(gè)參數(shù)是一個(gè)selector,也就是實(shí)例方法的名字,第三個(gè)參數(shù)是一個(gè)IMP類型的變量也就是函數(shù)實(shí)現(xiàn),需要傳入一個(gè)C函數(shù),這個(gè)函數(shù)至少有兩個(gè)參數(shù),一個(gè)是id self一個(gè)是SEL _cmd,第四個(gè)參數(shù)是函數(shù)類型。這里需要注意的是,對(duì)于相關(guān)實(shí)例方法的時(shí)候,參數(shù)Class cls可以是self,即類對(duì)象,而對(duì)于找不到相關(guān)類方法的時(shí)候,參數(shù)Class cls必須為元類,即object_getClass([self class])。

  • 消息轉(zhuǎn)發(fā)(所屬類動(dòng)態(tài)方法解析)

當(dāng)對(duì)象所屬類不能動(dòng)態(tài)添加方法后,runtime就會(huì)詢問(wèn)當(dāng)前的接受者是否要進(jìn)行消息轉(zhuǎn)發(fā)

  • 快速轉(zhuǎn)發(fā)

//實(shí)例方法調(diào)用會(huì)走這里
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(receiveSomething:)) {
        return [messages new];
    }
    return nil;
}
//類方法調(diào)用會(huì)走這里
+ (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(classMethod:)) {
        return [messages new];
    }
    return nil;
}
  • 標(biāo)準(zhǔn)(慢速)轉(zhuǎn)發(fā)

//實(shí)例方法調(diào)用會(huì)走這里
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *sig = [super methodSignatureForSelector:aSelector];
    if (!sig) {
        sig = [NSMethodSignature signatureWithObjCTypes:"v@:*"];
    }
    return sig;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    messages *me = [[messages alloc]init];
    if ([me respondsToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:me];
    }
}
//類方法調(diào)用會(huì)走這里
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *sig = [super methodSignatureForSelector:aSelector];
    if (!sig) {
        sig = [NSMethodSignature signatureWithObjCTypes:"v@:*"];
    }
    return sig;
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation {
    messages *me = [[messages alloc]init];
    if ([me respondsToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:me];
    }
}

既然forwardingTargetForSelector可以實(shí)現(xiàn)消息轉(zhuǎn)發(fā),為什么還要使用forwardInvocation作為消息管理中心呢?

  • forwardingTargetForSelector使用簡(jiǎn)單,不需要重寫methodSignatureForSelector,產(chǎn)生的消耗也比f(wàn)orwardInvocation低得多。
  • forwardingTargetForSelector無(wú)法獲取當(dāng)前的NSInvocation,或者說(shuō)少了一些可以操作的值。

而methodSignatureForSelector則是獲取到函數(shù)的簽名,包含了該函數(shù)的返回值以及參數(shù),只有methodSignatureForSelector返回不會(huì)為nil的時(shí)候,才會(huì)走forwardInvocation方法。

以上則為我所理解的runtime消息機(jī)制,有不理解和不足的的希望大家咨詢和補(bǔ)充。謝謝。

最后編輯于
?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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