- 本文首發(fā)于我的個(gè)人博客:「程序員充電站」
- 文章鏈接:「?jìng)魉烷T(mén)」
- 本文更新時(shí)間:2019-08-16 15:50:49
- 本次更新增加了 類方法相關(guān)的消息傳遞機(jī)制窒盐。
本文用來(lái)介紹 iOS 開(kāi)發(fā)中『Runtime』相關(guān)的基礎(chǔ)知識(shí)。通過(guò)本文,您將了解到:
- 什么是 Runtime鸠蚪?
- 消息機(jī)制的基本原理
- Runtime 中的概念解析(objc_msgSend 、Class精耐、Object券坞、Meta Class、Method)
- Runtime 消息轉(zhuǎn)發(fā)
- 消息發(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)行階段』分別做了什么绪颖?
- 編譯階段:
[receiver selector];
方法被編譯器轉(zhuǎn)換為:-
objc_msgSend(receiver秽荤,selector)
(不帶參數(shù)) -
objc_msgSend(recevier,selector柠横,org1窃款,org2,…)
(帶參數(shù))
-
- 運(yùn)行時(shí)階段:消息接受者
recevier
尋找對(duì)應(yīng)的selector
牍氛。- 通過(guò)
recevier
的isa 指針
找到recevier
的Class(類)
晨继; - 在
Class(類)
的cache(方法緩存)
的散列表中尋找對(duì)應(yīng)的IMP(方法實(shí)現(xiàn))
; - 如果在
cache(方法緩存)
中沒(méi)有找到對(duì)應(yīng)的IMP(方法實(shí)現(xiàn))
的話搬俊,就繼續(xù)在Class(類)
的method list(方法列表)
中找對(duì)應(yīng)的selector
紊扬,如果找到,填充到cache(方法緩存)
中唉擂,并返回selector
餐屎; - 如果在
Class(類)
中沒(méi)有找到這個(gè)selector
,就繼續(xù)在它的superClass(父類)
中尋找玩祟; - 一旦找到對(duì)應(yīng)的
selector
腹缩,直接執(zhí)行recevier
對(duì)應(yīng)selector
方法實(shí)現(xiàn)的IMP(方法實(shí)現(xiàn))
。 - 若找不到對(duì)應(yīng)的
selector
,消息被轉(zhuǎn)發(fā)或者臨時(shí)向recevier
添加這個(gè)selector
對(duì)應(yīng)的實(shí)現(xiàn)方法岳链,否則就會(huì)發(fā)生崩潰。
- 通過(guò)
在上述過(guò)程中涉及了好幾個(gè)新的概念:objc_msgSend
、isa 指針
锡搜、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)用差不多,流程如下:
- 通過(guò)類對(duì)象
isa 指針
找到所屬的Meta Class(元類)
芒炼; - 在
Meta Class(元類)
的method list(方法列表)
中找到對(duì)應(yīng)的selector
; - 執(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 指針
:
- 水平方向上洛史,每一級(jí)中的
實(shí)例對(duì)象
的isa 指針
指向了對(duì)應(yīng)的類對(duì)象
惯殊,而類對(duì)象
的isa 指針
指向了對(duì)應(yīng)的元類
。而所有元類的isa 指針
最終指向了NSObject 元類
也殖,因此NSObject 元類
也被稱為根元類
土思。 - 垂直方向上,
元類
的isa 指針
和父類元類
的isa 指針
都指向了根元類
忆嗜。而根元類
的isa 指針
又指向了自己己儒。
我們?cè)賮?lái)看 父類指針
:
-
類對(duì)象
的父類指針
指向了父類的類對(duì)象
,父類的類對(duì)象
又指向了根類的類對(duì)象
捆毫,根類的類對(duì)象
最終指向了 nil闪湾。 -
元類
的父類指針
指向了父類對(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è)變量遏插。
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
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ù)辰妙。
char * method_types; // 方法類型
方法類型 method_types
是個(gè)字符串鹰祸,用來(lái)存儲(chǔ)方法的參數(shù)類型和返回值類型。
到這里密浑,
Method
的結(jié)構(gòu)就已經(jīng)很清楚了蛙婴,Method
將SEL(方法名)
和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;
注意:
- 類方法和對(duì)象方法消息轉(zhuǎn)發(fā)第二步調(diào)用的方法不一樣,前者是
+forwardingTargetForSelector:
方法渴邦,后者是-forwardingTargetForSelector:
方法疯趟。- 這里
+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)用的是:
+ methodSignatureForSelector:
+ forwardInvocation:
+ doesNotRecognizeSelector:
對(duì)象方法調(diào)用的是:
- methodSignatureForSelector:
- forwardInvocation:
- 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)行的流程:
- 編譯階段:
[receiver selector];
方法被編譯器轉(zhuǎn)換為:-
objc_msgSend(receiver,selector)
(不帶參數(shù)) -
objc_msgSend(recevier右蕊,selector琼稻,org1,org2饶囚,…)
(帶參數(shù))
-
- 運(yùn)行時(shí)階段:消息接受者
recevier
尋找對(duì)應(yīng)的selector
帕翻。- 通過(guò)
recevier
的isa 指針
找到recevier
的class(類)
鸠补; - 在
Class(類)
的cache(方法緩存)
的散列表中尋找對(duì)應(yīng)的IMP(方法實(shí)現(xiàn))
; - 如果在
cache(方法緩存)
中沒(méi)有找到對(duì)應(yīng)的IMP(方法實(shí)現(xiàn))
的話嘀掸,就繼續(xù)在Class(類)
的method list(方法列表)
中找對(duì)應(yīng)的selector
紫岩,如果找到,填充到cache(方法緩存)
中睬塌,并返回selector
泉蝌; - 如果在
class(類)
中沒(méi)有找到這個(gè)selector
,就繼續(xù)在它的superclass(父類)
中尋找揩晴; - 一旦找到對(duì)應(yīng)的
selector
勋陪,直接執(zhí)行recevier
對(duì)應(yīng)selector
方法實(shí)現(xiàn)的IMP(方法實(shí)現(xiàn))
。 - 若找不到對(duì)應(yīng)的
selector
硫兰,Runtime 系統(tǒng)進(jìn)入消息轉(zhuǎn)發(fā)機(jī)制诅愚。
- 通過(guò)
- 運(yùn)行時(shí)消息轉(zhuǎn)發(fā)階段:
- 動(dòng)態(tài)解析:通過(guò)重寫(xiě)
+resolveInstanceMethod:
或者+resolveClassMethod:
方法,利用class_addMethod
方法添加其他函數(shù)實(shí)現(xiàn)劫映; - 消息接受者重定向:如果上一步添加其他函數(shù)實(shí)現(xiàn)违孝,可在當(dāng)前對(duì)象中利用
forwardingTargetForSelector:
方法將消息的接受者轉(zhuǎn)發(fā)給其他對(duì)象; - 消息重定向:如果上一步?jīng)]有返回值為
nil
泳赋,則利用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:
消息撒踪,程序也就崩潰了。
- 如果
- 動(dòng)態(tài)解析:通過(guò)重寫(xiě)
參考資料
- 文檔:Objective-C 運(yùn)行時(shí)(蘋(píng)果官方文檔)
- 文檔:Objective-C 運(yùn)行時(shí)編程指南(蘋(píng)果官方文檔)
- 博文:Runtime-iOS 運(yùn)行時(shí)基礎(chǔ)篇
- 博文:iOS Runtime 詳解
- 博文:新手也看得懂的 iOS Runtime 教程
以上就是 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』詳解(一)基礎(chǔ)知識(shí)
- iOS 開(kāi)發(fā):『Runtime』詳解(二)Method Swizzling
- iOS 開(kāi)發(fā):『Runtime』詳解(三)Category 底層原理
- iOS 開(kāi)發(fā):『Runtime』詳解(四)獲取類詳細(xì)屬性耕捞、方法
尚未完成:
- 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)頭注明『本文作者』和『本文鏈接』烫幕!