iOS 開(kāi)發(fā):『Runtime』詳解(一)基礎(chǔ)知識(shí)

本文用來(lái)介紹 iOS 開(kāi)發(fā)中『Runtime』相關(guān)的基礎(chǔ)知識(shí)。通過(guò)本文,您將了解到:

  1. 什么是 Runtime鸠蚪?
  2. 消息機(jī)制的基本原理
  3. Runtime 中的概念解析(objc_msgSend 、Class精耐、Object券坞、Meta Class、Method)
  4. Runtime 消息轉(zhuǎn)發(fā)
  5. 消息發(fā)送以及轉(zhuǎn)發(fā)機(jī)制總結(jié)

1. 什么是 Runtime同诫?

我們都知道,將源代碼轉(zhuǎn)換為可執(zhí)行的程序樟澜,通常要經(jīng)過(guò)三個(gè)步驟:編譯误窖、鏈接運(yùn)行秩贰。不同的編譯語(yǔ)言霹俺,在這三個(gè)步驟中所進(jìn)行的操作又有些不同。

C 語(yǔ)言 作為一門(mén)靜態(tài)類語(yǔ)言毒费,在編譯階段就已經(jīng)確定了所有變量的數(shù)據(jù)類型丙唧,同時(shí)也確定好了要調(diào)用的函數(shù),以及函數(shù)的實(shí)現(xiàn)觅玻。

Objective-C 語(yǔ)言 是一門(mén)動(dòng)態(tài)語(yǔ)言想际。在編譯階段并不知道變量的具體數(shù)據(jù)類型,也不知道所真正調(diào)用的哪個(gè)函數(shù)溪厘。只有在運(yùn)行時(shí)間才檢查變量的數(shù)據(jù)類型胡本,同時(shí)在運(yùn)行時(shí)才會(huì)根據(jù)函數(shù)名查找要調(diào)用的具體函數(shù)。這樣在程序沒(méi)運(yùn)行的時(shí)候畸悬,我們并不知道調(diào)用一個(gè)方法具體會(huì)發(fā)生什么侧甫。

Objective-C 語(yǔ)言 把一些決定性的工作從編譯階段、鏈接階段推遲到 運(yùn)行時(shí)階段 的機(jī)制,使得 Objective-C 變得更加靈活披粟。我們甚至可以在程序運(yùn)行的時(shí)候咒锻,動(dòng)態(tài)的去修改一個(gè)方法的實(shí)現(xiàn),這也為大為流行的『熱更新』提供了可能性僻爽。

而實(shí)現(xiàn) Objective-C 語(yǔ)言 運(yùn)行時(shí)機(jī)制 的一切基礎(chǔ)就是 Runtime虫碉。

Runtime 實(shí)際上是一個(gè)庫(kù),這個(gè)庫(kù)使我們可以在程序運(yùn)行時(shí)動(dòng)態(tài)的創(chuàng)建對(duì)象胸梆、檢查對(duì)象敦捧,修改類和對(duì)象的方法。


2. 消息機(jī)制的基本原理

Objective-C 語(yǔ)言 中碰镜,對(duì)象方法調(diào)用都是類似 [receiver selector]; 的形式兢卵,其本質(zhì)就是讓對(duì)象在運(yùn)行時(shí)發(fā)送消息的過(guò)程。

我們來(lái)看看方法調(diào)用 [receiver selector]; 在『編譯階段』和『運(yùn)行階段』分別做了什么绪颖?

  1. 編譯階段:[receiver selector]; 方法被編譯器轉(zhuǎn)換為:
    1. objc_msgSend(receiver秽荤,selector) (不帶參數(shù))
    2. objc_msgSend(recevier,selector柠横,org1窃款,org2,…)(帶參數(shù))
  2. 運(yùn)行時(shí)階段:消息接受者 recevier 尋找對(duì)應(yīng)的 selector牍氛。
    1. 通過(guò) recevierisa 指針 找到 recevierClass(類)晨继;
    2. Class(類)cache(方法緩存) 的散列表中尋找對(duì)應(yīng)的 IMP(方法實(shí)現(xiàn))
    3. 如果在 cache(方法緩存) 中沒(méi)有找到對(duì)應(yīng)的 IMP(方法實(shí)現(xiàn)) 的話搬俊,就繼續(xù)在 Class(類)method list(方法列表) 中找對(duì)應(yīng)的 selector紊扬,如果找到,填充到 cache(方法緩存) 中唉擂,并返回 selector餐屎;
    4. 如果在 Class(類) 中沒(méi)有找到這個(gè) selector,就繼續(xù)在它的 superClass(父類)中尋找玩祟;
    5. 一旦找到對(duì)應(yīng)的 selector腹缩,直接執(zhí)行 recevier 對(duì)應(yīng) selector 方法實(shí)現(xiàn)的 IMP(方法實(shí)現(xiàn))
    6. 若找不到對(duì)應(yīng)的 selector,消息被轉(zhuǎn)發(fā)或者臨時(shí)向 recevier 添加這個(gè) selector 對(duì)應(yīng)的實(shí)現(xiàn)方法岳链,否則就會(huì)發(fā)生崩潰。

在上述過(guò)程中涉及了好幾個(gè)新的概念:objc_msgSendisa 指針锡搜、Class(類)IMP(方法實(shí)現(xiàn)) 等蚕礼,下面我們來(lái)具體講解一下各個(gè)概念的含義叶圃。


3. Runtime 中的概念解析

3.1 objc_msgSend

所有 Objective-C 方法調(diào)用在編譯時(shí)都會(huì)轉(zhuǎn)化為對(duì) C 函數(shù) objc_msgSend 的調(diào)用。objc_msgSend(receiver,selector);[receiver selector]; 對(duì)應(yīng)的 C 函數(shù)甫煞。

3.2 Class(類)

objc/runtime.h 中菇曲,Class(類) 被定義為指向 objc_class 結(jié)構(gòu)體 的指針,objc_class 結(jié)構(gòu)體 的數(shù)據(jù)結(jié)構(gòu)如下:

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

struct objc_class {
    Class _Nonnull isa;                                          // objc_class 結(jié)構(gòu)體的實(shí)例指針

#if !__OBJC2__
    Class _Nullable super_class;                                 // 指向父類的指針
    const char * _Nonnull name;                                  // 類的名字
    long version;                                                // 類的版本信息抚吠,默認(rèn)為 0
    long info;                                                   // 類的信息常潮,供運(yùn)行期使用的一些位標(biāo)識(shí)
    long instance_size;                                          // 該類的實(shí)例變量大小;
    struct objc_ivar_list * _Nullable ivars;                     // 該類的實(shí)例變量列表
    struct objc_method_list * _Nullable * _Nullable methodLists; // 方法定義的列表
    struct objc_cache * _Nonnull cache;                          // 方法緩存
    struct objc_protocol_list * _Nullable protocols;             // 遵守的協(xié)議列表
#endif

};

從中可以看出,objc_class 結(jié)構(gòu)體 定義了很多變量:自身的所有實(shí)例變量(ivars)楷力、所有方法定義(methodLists)喊式、遵守的協(xié)議列表(protocols)等。objc_class 結(jié)構(gòu)體 存放的數(shù)據(jù)稱為 元數(shù)據(jù)(metadata)萧朝。

objc_class 結(jié)構(gòu)體 的第一個(gè)成員變量是 isa 指針岔留,isa 指針 保存的是所屬類的結(jié)構(gòu)體的實(shí)例的指針,這里保存的就是 objc_class 結(jié)構(gòu)體的實(shí)例指針检柬,而實(shí)例換個(gè)名字就是 對(duì)象献联。換句話說(shuō),Class(類) 的本質(zhì)其實(shí)就是一個(gè)對(duì)象何址,我們稱之為 類對(duì)象里逆。

3.3 Object(對(duì)象)

接下來(lái),我們?cè)賮?lái)看看 objc/objc.h 中關(guān)于 Object(對(duì)象) 的定義用爪。
Object(對(duì)象)被定義為 objc_object 結(jié)構(gòu)體原押,其數(shù)據(jù)結(jié)構(gòu)如下:

/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa;       // objc_object 結(jié)構(gòu)體的實(shí)例指針
};

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

這里的 id 被定義為一個(gè)指向 objc_object 結(jié)構(gòu)體 的指針。從中可以看出 objc_object 結(jié)構(gòu)體 只包含一個(gè) Class 類型的 isa 指針项钮。

換句話說(shuō)班眯,一個(gè) Object(對(duì)象)唯一保存的就是它所屬 Class(類) 的地址。當(dāng)我們對(duì)一個(gè)對(duì)象烁巫,進(jìn)行方法調(diào)用時(shí)署隘,比如 [receiver selector];,它會(huì)通過(guò) objc_object 結(jié)構(gòu)體isa 指針 去找對(duì)應(yīng)的 object_class 結(jié)構(gòu)體亚隙,然后在 object_class 結(jié)構(gòu)體methodLists(方法列表) 中找到我們調(diào)用的方法磁餐,然后執(zhí)行。

3.4 Meta Class(元類)

從上邊我們看出阿弃,對(duì)象(objc_object 結(jié)構(gòu)體)isa 指針 指向的是對(duì)應(yīng)的 類對(duì)象(object_class 結(jié)構(gòu)體)诊霹。那么 類對(duì)象(object_class 結(jié)構(gòu)體)的 isa 指針 又指向什么呢?

object_class 結(jié)構(gòu)體isa 指針 實(shí)際上指向的的是 類對(duì)象 自身的 Meta Class(元類)渣淳。

那么什么是 Meta Class(元類)脾还?

Meta Class(元類) 就是一個(gè)類對(duì)象所屬的 。一個(gè)對(duì)象所屬的類叫做 類對(duì)象入愧,而一個(gè)類對(duì)象所屬的類就叫做 元類鄙漏。

Runtime 中把類對(duì)象所屬類型就叫做 Meta Class(元類)嗤谚,用于描述類對(duì)象本身所具有的特征,而在元類的 methodLists 中怔蚌,保存了類的方法鏈表巩步,即所謂的「類方法」。并且類對(duì)象中的 isa 指針 指向的就是元類桦踊。每個(gè)類對(duì)象有且僅有一個(gè)與之相關(guān)的元類椅野。

2. 消息機(jī)制的基本原理 中我們講解了 對(duì)象方法的調(diào)用過(guò)程,我們是通過(guò)對(duì)象的 isa 指針 找到 對(duì)應(yīng)的 Class(類)籍胯;然后在 Class(類)method list(方法列表) 中找對(duì)應(yīng)的 selector 竟闪。

類方法的調(diào)用過(guò)程 和對(duì)象方法調(diào)用差不多,流程如下:

  1. 通過(guò)類對(duì)象 isa 指針 找到所屬的 Meta Class(元類)芒炼;
  2. Meta Class(元類)method list(方法列表) 中找到對(duì)應(yīng)的 selector;
  3. 執(zhí)行對(duì)應(yīng)的 selector瘫怜。

下面看一個(gè)示例:

NSString *testString = [NSString stringWithFormat:@"%d,%s",3, "test"];

上邊的示例中,stringWithFormat: 被發(fā)送給了 NSString 類本刽,NSString 類 通過(guò) isa 指針 找到 NSString 元類鲸湃,然后在該元類的方法列表中找到對(duì)應(yīng)的 stringWithFormat: 方法,然后執(zhí)行該方法子寓。

3.5 實(shí)例對(duì)象暗挑、類、元類之間的關(guān)系

上面斜友,我們講解了 實(shí)例對(duì)象(Object)炸裆、類(Class)Meta Class(元類) 的基本概念鲜屏,以及簡(jiǎn)單的指向關(guān)系烹看。下面我們通過(guò)一張圖來(lái)清晰地表示出這種關(guān)系。

我們先來(lái)看 isa 指針

  1. 水平方向上洛史,每一級(jí)中的 實(shí)例對(duì)象isa 指針 指向了對(duì)應(yīng)的 類對(duì)象惯殊,而 類對(duì)象isa 指針 指向了對(duì)應(yīng)的 元類。而所有元類的 isa 指針 最終指向了 NSObject 元類也殖,因此 NSObject 元類 也被稱為 根元類土思。
  2. 垂直方向上, 元類isa 指針父類元類isa 指針 都指向了 根元類忆嗜。而 根元類isa 指針 又指向了自己己儒。

我們?cè)賮?lái)看 父類指針

  1. 類對(duì)象父類指針 指向了 父類的類對(duì)象父類的類對(duì)象 又指向了 根類的類對(duì)象捆毫,根類的類對(duì)象 最終指向了 nil闪湾。
  2. 元類父類指針 指向了 父類對(duì)象的元類父類對(duì)象的元類父類指針指向了 根類對(duì)象的元類绩卤,也就是 根元類响谓。而 根元類父親指針 指向了 根類對(duì)象损合,最終指向了 nil省艳。

3.6 Method(方法)

object_class 結(jié)構(gòu)體methodLists(方法列表)中存放的元素就是 Method(方法)娘纷。

先來(lái)看下 objc/runtime.h 中,表示 Method(方法)objc_method 結(jié)構(gòu)體 的數(shù)據(jù)結(jié)構(gòu):

/// An opaque type that represents a method in a class definition.
/// 代表類定義中一個(gè)方法的不透明類型
typedef struct objc_method *Method;

struct objc_method {
    SEL _Nonnull method_name;                    // 方法名
    char * _Nullable method_types;               // 方法類型
    IMP _Nonnull method_imp;                     // 方法實(shí)現(xiàn)
};

可以看到跋炕,objc_method 結(jié)構(gòu)體 中包含了 method_name(方法名)赖晶,method_types(方法類型)method_imp(方法實(shí)現(xiàn))。下面辐烂,我們來(lái)了解下這三個(gè)變量遏插。

  1. SEL method_name; // 方法名
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;

SEL 是一個(gè)指向 objc_selector 結(jié)構(gòu)體 的指針,但是在 runtime 相關(guān)頭文件中并沒(méi)有找到明確的定義纠修。不過(guò)胳嘲,通過(guò)測(cè)試我們可以得出: SEL 只是一個(gè)保存方法名的字符串。

SEL sel = @selector(viewDidLoad);
NSLog(@"%s", sel);              // 輸出:viewDidLoad
SEL sel1 = @selector(test);
NSLog(@"%s", sel1);             // 輸出:test
  1. IMP method_imp; // 方法實(shí)現(xiàn)
/// A pointer to the function of a method implementation. 
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
#endif

IMP 的實(shí)質(zhì)是一個(gè)函數(shù)指針扣草,所指向的就是方法的實(shí)現(xiàn)了牛。IMP用來(lái)找到函數(shù)地址,然后執(zhí)行函數(shù)辰妙。

  1. char * method_types; // 方法類型

方法類型 method_types 是個(gè)字符串鹰祸,用來(lái)存儲(chǔ)方法的參數(shù)類型和返回值類型。

到這里密浑, Method 的結(jié)構(gòu)就已經(jīng)很清楚了蛙婴,MethodSEL(方法名)IMP(函數(shù)指針) 關(guān)聯(lián)起來(lái),當(dāng)對(duì)一個(gè)對(duì)象發(fā)送消息時(shí)尔破,會(huì)通過(guò)給出的 SEL(方法名) 去找到 IMP(函數(shù)指針) 街图,然后執(zhí)行。


4. Runtime 消息轉(zhuǎn)發(fā)

2. 消息機(jī)制的基本原理 最后一步中我們提到:若找不到對(duì)應(yīng)的 selector懒构,消息被轉(zhuǎn)發(fā)或者臨時(shí)向 recevier 添加這個(gè) selector 對(duì)應(yīng)的實(shí)現(xiàn)方法餐济,否則就會(huì)發(fā)生崩潰。

當(dāng)一個(gè)方法找不到的時(shí)候痴脾,Runtime 提供了 消息動(dòng)態(tài)解析颤介、消息接受者重定向消息重定向 等三步處理消息赞赖,具體流程如下圖所示:

4.1 消息動(dòng)態(tài)解析

Objective-C 運(yùn)行時(shí)會(huì)調(diào)用 +resolveInstanceMethod: 或者 +resolveClassMethod:滚朵,讓你有機(jī)會(huì)提供一個(gè)函數(shù)實(shí)現(xiàn)。前者在 對(duì)象方法未找到時(shí) 調(diào)用前域,后者在 類方法未找到時(shí) 調(diào)用辕近。我們可以通過(guò)重寫(xiě)這兩個(gè)方法,添加其他函數(shù)實(shí)現(xiàn)匿垄,并返回 YES移宅, 那運(yùn)行時(shí)系統(tǒng)就會(huì)重新啟動(dòng)一次消息發(fā)送的過(guò)程归粉。

主要用的的方法如下:

// 類方法未找到時(shí)調(diào)起,可以在此添加方法實(shí)現(xiàn)
+ (BOOL)resolveClassMethod:(SEL)sel;
// 對(duì)象方法未找到時(shí)調(diào)起漏峰,可以在此添加方法實(shí)現(xiàn)
+ (BOOL)resolveInstanceMethod:(SEL)sel;

/** 
 * class_addMethod    向具有給定名稱和實(shí)現(xiàn)的類中添加新方法
 * @param cls         被添加方法的類
 * @param name        selector 方法名
 * @param imp         實(shí)現(xiàn)方法的函數(shù)指針
 * @param types imp   指向函數(shù)的返回值與參數(shù)類型
 * @return            如果添加方法成功返回 YES糠悼,否則返回 NO
 */
BOOL class_addMethod(Class cls, SEL name, IMP imp, 
                const char * _Nullable types);

舉個(gè)例子:

#import "ViewController.h"
#include "objc/runtime.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 執(zhí)行 fun 函數(shù)
    [self performSelector:@selector(fun)];
}

// 重寫(xiě) resolveInstanceMethod: 添加對(duì)象方法實(shí)現(xiàn)
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(fun)) { // 如果是執(zhí)行 fun 函數(shù),就動(dòng)態(tài)解析浅乔,指定新的 IMP
        class_addMethod([self class], sel, (IMP)funMethod, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

void funMethod(id obj, SEL _cmd) {
    NSLog(@"funMethod"); //新的 fun 函數(shù)
}

@end

打印結(jié)果:
2019-06-12 10:25:39.848260+0800 runtime[14884:7977579] funMethod

從上邊的例子中倔喂,我們可以看出,雖然我們沒(méi)有實(shí)現(xiàn) fun 方法靖苇,但是通過(guò)重寫(xiě) resolveInstanceMethod: 席噩,利用 class_addMethod 方法添加對(duì)象方法實(shí)現(xiàn) funMethod 方法,并執(zhí)行贤壁。從打印結(jié)果來(lái)看悼枢,成功調(diào)起了funMethod 方法。

我們注意到 class_addMethod 方法中的特殊參數(shù) v@:脾拆,具體可參考官方文檔中關(guān)于 Type Encodings 的說(shuō)明:傳送門(mén)


4.2 消息接受者重定向

如果上一步中 +resolveInstanceMethod: 或者 +resolveClassMethod: 沒(méi)有添加其他函數(shù)實(shí)現(xiàn)馒索,運(yùn)行時(shí)就會(huì)進(jìn)行下一步:消息接受者重定向。

如果當(dāng)前對(duì)象實(shí)現(xiàn)了 -forwardingTargetForSelector: 或者 +forwardingTargetForSelector: 方法假丧,Runtime 就會(huì)調(diào)用這個(gè)方法双揪,允許我們將消息的接受者轉(zhuǎn)發(fā)給其他對(duì)象。

其中用到的方法:

// 重定向類方法的消息接收者包帚,返回一個(gè)類或?qū)嵗龑?duì)象
+ (id)forwardingTargetForSelector:(SEL)aSelector;
// 重定向方法的消息接收者渔期,返回一個(gè)類或?qū)嵗龑?duì)象
- (id)forwardingTargetForSelector:(SEL)aSelector;

注意:

  1. 類方法和對(duì)象方法消息轉(zhuǎn)發(fā)第二步調(diào)用的方法不一樣,前者是+forwardingTargetForSelector: 方法渴邦,后者是 -forwardingTargetForSelector: 方法疯趟。
  2. 這里+resolveInstanceMethod: 或者 +resolveClassMethod:無(wú)論是返回 YES,還是返回 NO谋梭,只要其中沒(méi)有添加其他函數(shù)實(shí)現(xiàn)信峻,運(yùn)行時(shí)都會(huì)進(jìn)行下一步。

舉個(gè)例子:

#import "ViewController.h"
#include "objc/runtime.h"

@interface Person : NSObject

- (void)fun;

@end

@implementation Person

- (void)fun {
    NSLog(@"fun");
}

@end

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 執(zhí)行 fun 方法
    [self performSelector:@selector(fun)];
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    return YES; // 為了進(jìn)行下一步 消息接受者重定向
}

// 消息接受者重定向
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(fun)) {
        return [[Person alloc] init];
        // 返回 Person 對(duì)象瓮床,讓 Person 對(duì)象接收這個(gè)消息
    }
    
    return [super forwardingTargetForSelector:aSelector];
}

打印結(jié)果:
2019-06-12 17:34:05.027800+0800 runtime[19495:8232512] fun

可以看到盹舞,雖然當(dāng)前 ViewController 沒(méi)有實(shí)現(xiàn) fun 方法,+resolveInstanceMethod: 也沒(méi)有添加其他函數(shù)實(shí)現(xiàn)隘庄。但是我們通過(guò) forwardingTargetForSelector 把當(dāng)前 ViewController 的方法轉(zhuǎn)發(fā)給了 Person 對(duì)象去執(zhí)行了踢步。打印結(jié)果也證明我們成功實(shí)現(xiàn)了轉(zhuǎn)發(fā)。

我們通過(guò) forwardingTargetForSelector 可以修改消息的接收者丑掺,該方法返回參數(shù)是一個(gè)對(duì)象获印,如果這個(gè)對(duì)象是不是 nil,也不是 self街州,系統(tǒng)會(huì)將運(yùn)行的消息轉(zhuǎn)發(fā)給這個(gè)對(duì)象執(zhí)行兼丰。否則玻孟,繼續(xù)進(jìn)行下一步:消息重定向流程。


4.3 消息重定向

如果經(jīng)過(guò)消息動(dòng)態(tài)解析鳍征、消息接受者重定向黍翎,Runtime 系統(tǒng)還是找不到相應(yīng)的方法實(shí)現(xiàn)而無(wú)法響應(yīng)消息,Runtime 系統(tǒng)會(huì)利用 -methodSignatureForSelector: 或者 +methodSignatureForSelector: 方法獲取函數(shù)的參數(shù)和返回值類型蟆技。

  • 如果 methodSignatureForSelector: 返回了一個(gè) NSMethodSignature 對(duì)象(函數(shù)簽名)玩敏,Runtime 系統(tǒng)就會(huì)創(chuàng)建一個(gè) NSInvocation 對(duì)象,并通過(guò) forwardInvocation: 消息通知當(dāng)前對(duì)象质礼,給予此次消息發(fā)送最后一次尋找 IMP 的機(jī)會(huì)。
  • 如果 methodSignatureForSelector: 返回 nil织阳。則 Runtime 系統(tǒng)會(huì)發(fā)出 doesNotRecognizeSelector: 消息眶蕉,程序也就崩潰了。

所以我們可以在 forwardInvocation: 方法中對(duì)消息進(jìn)行轉(zhuǎn)發(fā)唧躲。

注意:類方法和對(duì)象方法消息轉(zhuǎn)發(fā)第三步調(diào)用的方法同樣不一樣造挽。
類方法調(diào)用的是:

  1. + methodSignatureForSelector:
  2. + forwardInvocation:
  3. + doesNotRecognizeSelector:

對(duì)象方法調(diào)用的是:

  1. - methodSignatureForSelector:
  2. - forwardInvocation:
  3. - doesNotRecognizeSelector:

用到的方法:

// 獲取類方法函數(shù)的參數(shù)和返回值類型,返回簽名
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;

// 類方法消息重定向
+ (void)forwardInvocation:(NSInvocation *)anInvocation弄痹;

// 獲取對(duì)象方法函數(shù)的參數(shù)和返回值類型饭入,返回簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;

// 對(duì)象方法消息重定向
- (void)forwardInvocation:(NSInvocation *)anInvocation;

舉個(gè)例子:

#import "ViewController.h"
#include "objc/runtime.h"

@interface Person : NSObject

- (void)fun;

@end

@implementation Person

- (void)fun {
    NSLog(@"fun");
}

@end

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 執(zhí)行 fun 函數(shù)
    [self performSelector:@selector(fun)];
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    return YES; // 為了進(jìn)行下一步 消息接受者重定向
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
    return nil; // 為了進(jìn)行下一步 消息重定向
}

// 獲取函數(shù)的參數(shù)和返回值類型肛真,返回簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if ([NSStringFromSelector(aSelector) isEqualToString:@"fun"]) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    
    return [super methodSignatureForSelector:aSelector];
}

// 消息重定向
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL sel = anInvocation.selector;   // 從 anInvocation 中獲取消息
    
    Person *p = [[Person alloc] init];

    if([p respondsToSelector:sel]) {   // 判斷 Person 對(duì)象方法是否可以響應(yīng) sel
        [anInvocation invokeWithTarget:p];  // 若可以響應(yīng)谐丢,則將消息轉(zhuǎn)發(fā)給其他對(duì)象處理
    } else {
        [self doesNotRecognizeSelector:sel];  // 若仍然無(wú)法響應(yīng),則報(bào)錯(cuò):找不到響應(yīng)方法
    }
}
@end

打印結(jié)果:
2019-06-13 13:23:06.935624+0800 runtime[30032:8724248] fun

可以看到蚓让,我們?cè)?-forwardInvocation: 方法里面讓 Person 對(duì)象去執(zhí)行了 fun 函數(shù)乾忱。

既然 -forwardingTargetForSelector:-forwardInvocation: 都可以將消息轉(zhuǎn)發(fā)給其他對(duì)象處理,那么兩者的區(qū)別在哪历极?

區(qū)別就在于 -forwardingTargetForSelector: 只能將消息轉(zhuǎn)發(fā)給一個(gè)對(duì)象窄瘟。而 -forwardInvocation: 可以將消息轉(zhuǎn)發(fā)給多個(gè)對(duì)象。

以上就是 Runtime 消息轉(zhuǎn)發(fā)的整個(gè)流程趟卸。

結(jié)合之前講的 2. 消息機(jī)制的基本原理蹄葱,就構(gòu)成了整個(gè)消息發(fā)送以及轉(zhuǎn)發(fā)的流程。下面我們來(lái)總結(jié)一下整個(gè)流程锄列。


5. 消息發(fā)送以及轉(zhuǎn)發(fā)機(jī)制總結(jié)

調(diào)用 [receiver selector]; 后图云,進(jìn)行的流程:

  1. 編譯階段:[receiver selector]; 方法被編譯器轉(zhuǎn)換為:
    1. objc_msgSend(receiver,selector) (不帶參數(shù))
    2. objc_msgSend(recevier右蕊,selector琼稻,org1,org2饶囚,…)(帶參數(shù))
  2. 運(yùn)行時(shí)階段:消息接受者 recevier 尋找對(duì)應(yīng)的 selector帕翻。
    1. 通過(guò) recevierisa 指針 找到 recevierclass(類)鸠补;
    2. Class(類)cache(方法緩存) 的散列表中尋找對(duì)應(yīng)的 IMP(方法實(shí)現(xiàn))
    3. 如果在 cache(方法緩存) 中沒(méi)有找到對(duì)應(yīng)的 IMP(方法實(shí)現(xiàn)) 的話嘀掸,就繼續(xù)在 Class(類)method list(方法列表) 中找對(duì)應(yīng)的 selector紫岩,如果找到,填充到 cache(方法緩存) 中睬塌,并返回 selector泉蝌;
    4. 如果在 class(類) 中沒(méi)有找到這個(gè) selector,就繼續(xù)在它的 superclass(父類)中尋找揩晴;
    5. 一旦找到對(duì)應(yīng)的 selector勋陪,直接執(zhí)行 recevier 對(duì)應(yīng) selector 方法實(shí)現(xiàn)的 IMP(方法實(shí)現(xiàn))
    6. 若找不到對(duì)應(yīng)的 selector硫兰,Runtime 系統(tǒng)進(jìn)入消息轉(zhuǎn)發(fā)機(jī)制诅愚。
  3. 運(yùn)行時(shí)消息轉(zhuǎn)發(fā)階段:
    1. 動(dòng)態(tài)解析:通過(guò)重寫(xiě) +resolveInstanceMethod: 或者 +resolveClassMethod:方法,利用 class_addMethod方法添加其他函數(shù)實(shí)現(xiàn)劫映;
    2. 消息接受者重定向:如果上一步添加其他函數(shù)實(shí)現(xiàn)违孝,可在當(dāng)前對(duì)象中利用 forwardingTargetForSelector: 方法將消息的接受者轉(zhuǎn)發(fā)給其他對(duì)象;
    3. 消息重定向:如果上一步?jīng)]有返回值為 nil泳赋,則利用 methodSignatureForSelector:方法獲取函數(shù)的參數(shù)和返回值類型雌桑。
      1. 如果 methodSignatureForSelector: 返回了一個(gè) NSMethodSignature 對(duì)象(函數(shù)簽名),Runtime 系統(tǒng)就會(huì)創(chuàng)建一個(gè) NSInvocation 對(duì)象祖今,并通過(guò) forwardInvocation: 消息通知當(dāng)前對(duì)象校坑,給予此次消息發(fā)送最后一次尋找 IMP 的機(jī)會(huì)。
      2. 如果 methodSignatureForSelector: 返回 nil衅鹿。則 Runtime 系統(tǒng)會(huì)發(fā)出 doesNotRecognizeSelector: 消息撒踪,程序也就崩潰了。

參考資料


以上就是 iOS 開(kāi)發(fā):『Runtime』詳解(一):基礎(chǔ)知識(shí) 的所有內(nèi)容了大渤。
整篇文章主要就講了一件事:消息發(fā)送以及轉(zhuǎn)發(fā)機(jī)制的原理和流程制妄。這也是 Runtime 系統(tǒng)的工作原理。

下一篇筆者準(zhǔn)備講一下『Runtime』的實(shí)戰(zhàn)應(yīng)用泵三。


iOS 開(kāi)發(fā):『Runtime』詳解 系列文章:

尚未完成:

  • iOS 開(kāi)發(fā):『Runtime』詳解(五)Crash 防護(hù)系統(tǒng)
  • iOS 開(kāi)發(fā):『Runtime』詳解(六)Objective-C 2.0 結(jié)構(gòu)解析
  • iOS 開(kāi)發(fā):『Runtime』詳解(七)KVO 底層實(shí)現(xiàn)

  • 本文作者: 行走少年郎
  • 本文鏈接: http://www.reibang.com/p/633e5d8386a8
  • 版權(quán)聲明: 本文章采用 CC BY-NC-SA 3.0 許可協(xié)議。轉(zhuǎn)載請(qǐng)?jiān)谖淖珠_(kāi)頭注明『本文作者』和『本文鏈接』烫幕!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末俺抽,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子较曼,更是在濱河造成了極大的恐慌磷斧,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異弛饭,居然都是意外死亡冕末,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)侣颂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)档桃,“玉大人,你說(shuō)我怎么就攤上這事憔晒≡逡蓿” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵拒担,是天一觀的道長(zhǎng)嘹屯。 經(jīng)常有香客問(wèn)我,道長(zhǎng)澎蛛,這世上最難降的妖魔是什么抚垄? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮谋逻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘桐经。我一直安慰自己毁兆,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布阴挣。 她就那樣靜靜地躺著气堕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪畔咧。 梳的紋絲不亂的頭發(fā)上茎芭,一...
    開(kāi)封第一講書(shū)人閱讀 49,111評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音誓沸,去河邊找鬼梅桩。 笑死,一個(gè)胖子當(dāng)著我的面吹牛拜隧,可吹牛的內(nèi)容都是我干的宿百。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼洪添,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼垦页!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起干奢,我...
    開(kāi)封第一講書(shū)人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤痊焊,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體薄啥,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡辕羽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了罪佳。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片逛漫。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖赘艳,靈堂內(nèi)的尸體忽然破棺而出酌毡,到底是詐尸還是另有隱情,我是刑警寧澤蕾管,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布枷踏,位于F島的核電站,受9級(jí)特大地震影響掰曾,放射性物質(zhì)發(fā)生泄漏旭蠕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一旷坦、第九天 我趴在偏房一處隱蔽的房頂上張望掏熬。 院中可真熱鬧,春花似錦秒梅、人聲如沸旗芬。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)疮丛。三九已至,卻和暖如春辆它,著一層夾襖步出監(jiān)牢的瞬間誊薄,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工锰茉, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留呢蔫,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓洞辣,卻偏偏與公主長(zhǎng)得像咐刨,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子扬霜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 簡(jiǎn)介 Runtime 又叫運(yùn)行時(shí)定鸟,是一套底層的 C 語(yǔ)言 API,其為 iOS 內(nèi)部的核心之一著瓶,我們平時(shí)編寫(xiě)的 O...
    專業(yè)男神經(jīng)閱讀 902評(píng)論 0 2
  • 本文轉(zhuǎn)載自:http://yulingtianxia.com/blog/2014/11/05/objective-...
    ant_flex閱讀 748評(píng)論 0 1
  • 我們常常會(huì)聽(tīng)說(shuō) Objective-C 是一門(mén)動(dòng)態(tài)語(yǔ)言联予,那么這個(gè)「動(dòng)態(tài)」表現(xiàn)在哪呢?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,176評(píng)論 0 7
  • Objective-C語(yǔ)言是一門(mén)動(dòng)態(tài)語(yǔ)言,他將很多靜態(tài)語(yǔ)言在編譯和鏈接時(shí)期做的事情放到了運(yùn)行時(shí)來(lái)處理沸久。這種動(dòng)態(tài)語(yǔ)言...
    tigger丨閱讀 1,382評(píng)論 0 8
  • 今天一回頭看季眷,特種兵學(xué)習(xí)接近尾聲,今天63人卷胯,我們一起突破全體簽到55s子刮,在這十九天的日子里,看到了許許多多...
    肌膚管理師香香閱讀 152評(píng)論 0 0