Runtime-消息機(jī)制

Objective-C是一門(mén)動(dòng)態(tài)語(yǔ)言,類型的判斷、類的成員變量、方法的內(nèi)存地址都是在程序的運(yùn)行階段才最終確定,并且還能動(dòng)態(tài)的添加成員變量和方法。這意味著即使調(diào)用對(duì)象一個(gè)沒(méi)有實(shí)現(xiàn)的方法,編譯也能通過(guò),甚至一個(gè)對(duì)象它是什么類型并不是表面我們所看到的那樣,只有運(yùn)行之后才能決定其真正的類型。因此OC具有多態(tài)動(dòng)態(tài)類型、動(dòng)態(tài)綁定特性。

正是因?yàn)槠?code>動(dòng)態(tài)特性,在OC中調(diào)用方法并不是一個(gè)簡(jiǎn)單的函數(shù)調(diào)用過(guò)程,它并不直接調(diào)用方法,而是運(yùn)用Runtime機(jī)制實(shí)現(xiàn)函數(shù)調(diào)用,稱之為消息發(fā)送。

Runtime

Runime運(yùn)行時(shí),是OC中一套底層的C語(yǔ)言API,底層都是基于它來(lái)實(shí)現(xiàn)的。在運(yùn)行時(shí),我們所編寫(xiě)的代碼會(huì)轉(zhuǎn)換為C語(yǔ)言運(yùn)行。具體內(nèi)容可以參考:runtime概述

在OC中發(fā)送一條消息:[receiver message],
會(huì)轉(zhuǎn)為調(diào)用底層函數(shù):id objc_msgSend(id self, SEL op, ...)
讓我們先看看這個(gè)函數(shù)的參數(shù)和返回值
id在OC中表示任意類型的對(duì)象,其結(jié)構(gòu)如下:

/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;

顯然id是一個(gè)objc_objec結(jié)構(gòu)體類型指針,其內(nèi)部有一個(gè)Class類型的isa指針,指向?qū)?yīng)的類。
SEL是對(duì)方法的包裝,返回對(duì)應(yīng)的編號(hào)

struct objc_class {
    Class _Nonnull isa             指向自身對(duì)應(yīng)的類;
#if !__OBJC2__
    Class _Nullable super_class            指向父類                  
    const char * _Nonnull name                               
    long version                                             
    long info                                                
    long instance_size                                       
    struct objc_ivar_list * _Nullable ivars                  
    struct objc_method_list * _Nullable * _Nullable methodLists    類中的方法信息列表                 
    struct objc_cache * _Nonnull cache         調(diào)用過(guò)的方法信息列表             
    struct objc_protocol_list * _Nullable protocols          
#endif
}

上面是class的結(jié)構(gòu)

消息發(fā)送過(guò)程一 --- 函數(shù)在類中實(shí)現(xiàn)

當(dāng)發(fā)送一條消息時(shí),object_getClass(id obj)通過(guò)id的isa指針找到對(duì)象的對(duì)應(yīng)的類class,在class中先去cache中 通過(guò)SEL查找對(duì)應(yīng)函數(shù)method(cache中method列表是以SEL為key通過(guò)hash表來(lái)存儲(chǔ)的,這樣能提高函數(shù)查找速度),若 cache中未找到,方法列表methodList中查找,若能找到,則將method加 入到cache中,以方便下次查找。否則到對(duì)應(yīng)的super_class中查找,如此循環(huán)直到NSObject。

我們具體看OC如何用runtime實(shí)現(xiàn)函數(shù)調(diào)用:

自定義Person對(duì)象
@interface Person : NSObject
+ (void)eat;
- (void)eat;
- (void)run:(int)age;
@end
@implementation Person
+ (void)eat
{
    NSLog(@"類方法-吃東西");
}
- (void)eat
{
     NSLog(@"實(shí)例方法-吃東西");
}
- (void)run:(int)age
{
    NSLog(@"跑了%d米",age);
}
@end
-------------------------UIViewController-----------------------------
- (void)viewDidLoad {
    [super viewDidLoad];

    Person *person = [[Person alloc] init];
    objc_msgSend(person, @selector(eat));
    objc_msgSend(person, @selector(run:),10);

    Class personClass = [Person class];
    objc_msgSend(personClass, @selector(eat));
}
-------------------------console-----------------------------
[1373:101045] 實(shí)例方法-吃東西
[1373:101045] 跑了10米
[1373:101045] 類方法-吃東西

以上就是發(fā)送消息調(diào)用類中實(shí)現(xiàn)的方法的過(guò)程。


消息發(fā)送過(guò)程二 --- 函數(shù)在類中未實(shí)現(xiàn),動(dòng)態(tài)解析方法

當(dāng)向receiver發(fā)送message時(shí),若SEL對(duì)應(yīng)的函數(shù)未在類或者類的父類中實(shí)現(xiàn),程序并不會(huì)立即crash,而是對(duì)message進(jìn)一步轉(zhuǎn)發(fā),調(diào)用_objc_msgForward(id receiver, SEL sel, ...),具體表現(xiàn)在類中對(duì)應(yīng)的方法為:

1.+ resolveInstanceMethod:(SEL)sel // 對(duì)應(yīng)實(shí)例方法
  + resolveClassMethod:(SEL)sel // 對(duì)應(yīng)類方法
2.- (id)forwardingTargetForSelector:(SEL)aSelector
3.- (void)forwardInvocation:(NSInvocation *)anInvocation

注意:以上方法優(yōu)先級(jí)依次遞減,高優(yōu)先級(jí)方法消息轉(zhuǎn)發(fā)成功不會(huì)再執(zhí)行低優(yōu)先級(jí)方法

現(xiàn)在為Person實(shí)例對(duì)象發(fā)送一條@selector(wy_sleep:)消息:

[person performSelector:@selector(wy_sleep:) withObject:@100];

因?yàn)?code>person未實(shí)現(xiàn)@selector(wy_sleep:),消息轉(zhuǎn)發(fā)轉(zhuǎn)而調(diào)用+ (BOOL)resolveInstanceMethod:(SEL)sel:,判斷類是在運(yùn)行時(shí)動(dòng)態(tài)的添加方法實(shí)現(xiàn)

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(wy_sleep:)) {
        
        /*
         cls:給哪個(gè)類添加方法
         SEL:添加方法的方法編號(hào)是什么
         IMP:方法實(shí)現(xiàn),函數(shù)入口,函數(shù)名
         types:方法類型
         最后一個(gè)參數(shù)請(qǐng)查看文檔Help - API reference - runtime
         */
        class_addMethod([self class], sel, (IMP)wy_sleep, "v@:@");
        
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

void wy_sleep(id self, SEL _cmd, id param1){
    
    NSLog(@"調(diào)用eat %@ %@ %@",self,NSStringFromSelector(_cmd),param1);
}

-------------------------console-----------------------------
[1553:136714] 調(diào)用wy_sleep <Person: 0x60000003ed40> wy_sleep: 100

其中class_addMethod(Class cls, SEL name, IMP imp, const char *types)為類添加新的方法實(shí)現(xiàn)。


消息發(fā)送過(guò)程三 --- 函數(shù)在類中未實(shí)現(xiàn)且無(wú)動(dòng)態(tài)解析,快速轉(zhuǎn)發(fā)消息

當(dāng)調(diào)用的方法沒(méi)有在類中實(shí)現(xiàn),且沒(méi)有動(dòng)態(tài)添加時(shí),則消息繼續(xù)轉(zhuǎn)發(fā),判斷類中是否預(yù)備了備用的消息接受者

- (id)forwardingTargetForSelector:(SEL)aSelector
簡(jiǎn)單、快速,但只能只能指定一個(gè)轉(zhuǎn)發(fā)對(duì)象,無(wú)法對(duì)消息進(jìn)行操作

 定義對(duì)象A、B
-------------------------A-----------------------------
@interface A : NSObject
- (void)prinAName;
@end
@implementation A
- (void)prinAName
{
    NSLog(@"Hello World! My name is A");
}
@end

-------------------------B-----------------------------
@interface B : NSObject
- (void)prinBName;
@end
@implementation B
- (void)prinBName
{
    NSLog(@"Hello World! My name is B");
}
@end

實(shí)現(xiàn)Person中的的快速消息轉(zhuǎn)發(fā):

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (class_respondsToSelector([A class], aSelector)) {
        return [[A alloc] init];
    }
    
    B *b = [[B alloc] init];
    if ([b respondsToSelector:aSelector]) {
        return b;
    }
    return [super forwardingTargetForSelector:aSelector];
}

向person實(shí)例發(fā)送相應(yīng)的消息:

[person performSelector:@selector(prinAName)];
[person performSelector:@selector(prinBName)];

輸出結(jié)果:
[1962:235819] Hello World! My name is A
[1962:235819] Hello World! My name is B


消息發(fā)送過(guò)程四 --- 函數(shù)在類中未實(shí)現(xiàn)且無(wú)動(dòng)態(tài)解析,完整的消息轉(zhuǎn)發(fā)

除了設(shè)置備用消息接受者,快速轉(zhuǎn)發(fā)消息外,還能將完整的消息轉(zhuǎn)發(fā)給實(shí)現(xiàn)的對(duì)象

-(void)forwardInvocation:(NSInvocation *)anInvocation
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSMethodSignature *methodSignature = [A instanceMethodSignatureForSelector:aSelector];
    if (methodSignature == nil) {
        methodSignature = [B instanceMethodSignatureForSelector:aSelector];
    }
    if (methodSignature == nil) {
        methodSignature = [super methodSignatureForSelector:aSelector];
    }
    return methodSignature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    SEL selector = [anInvocation selector];
    A *a = [[A alloc] init];
    B *b = [[B alloc] init];
    if ([a respondsToSelector:selector]) {
        [anInvocation invokeWithTarget:a];
    } else if ([b respondsToSelector:selector]) {
        [anInvocation invokeWithTarget:b];
    } else {
        [super forwardInvocation:anInvocation];
    }
}

消息發(fā)送過(guò)程五 --- Crash

當(dāng)前面消息發(fā)送的4個(gè)過(guò)程都沒(méi)有實(shí)現(xiàn)時(shí),程序調(diào)用- (void)doesNotRecognizeSelector:(SEL)aSelector崩潰

- (void)doesNotRecognizeSelector:(SEL)aSelector
{
    NSLog(@"無(wú)法實(shí)現(xiàn)方法,crash");
}

總結(jié):
OC中函數(shù)調(diào)用通過(guò)發(fā)送消息實(shí)現(xiàn),分為五個(gè)步驟,如下:

消息發(fā)送流程

?著作權(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)容

  • 從異常說(shuō)起 我們都知道,在iOS中存在這么一個(gè)通用類類型id,它可以用來(lái)表示任何對(duì)象的類型 —— 這意味著我們使用...
    sindri的小巢閱讀 4,658評(píng)論 15 43
  • objc_msgSend OC中的實(shí)例對(duì)象調(diào)用一個(gè)方法稱作消息傳遞,例如: 代碼中,我們將inkInstance這...
    xl_b973閱讀 536評(píng)論 1 6
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,053評(píng)論 0 9
  • 雖然swift 慢慢會(huì)越用越多,但憑借oc 強(qiáng)大的runtime機(jī)制,oc依舊是現(xiàn)在的中流砥柱.只做記錄探討 加深...
    zqzal閱讀 259評(píng)論 0 1
  • 《初夏書(shū)感》 春與人俱老,花隨夢(mèng)已空。游蜂黏落蕊,輕燕接飛蟲(chóng)。桑悴知蠶起,牲肥賽麥...
    土豪總部閱讀 417評(píng)論 0 0

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