>Runtime 介紹
C 語言 作為一門靜態(tài)類語言,在編譯階段就已經(jīng)確定了所有變量的數(shù)據(jù)類型,同時(shí)也確定好了要調(diào)用的函數(shù),以及函數(shù)的實(shí)現(xiàn)。而 Objective-C 語言是一門動(dòng)態(tài)語言。在編譯階段并不知道變量的具體數(shù)據(jù)類型,也不知道所真正調(diào)用的哪個(gè)函數(shù)。只有在運(yùn)行時(shí)間才檢查變量的數(shù)據(jù)類型,同時(shí)在運(yùn)行時(shí)才會(huì)根據(jù)函數(shù)名查找要調(diào)用的具體函數(shù)。這樣在程序沒運(yùn)行的時(shí)候,我們并不知道調(diào)用一個(gè)方法具體會(huì)發(fā)生什么。
Objective-C 擴(kuò)展了 C 語言,并加入了面向?qū)ο筇匦院?Smalltalk 式的消息傳遞機(jī)制。而這個(gè)擴(kuò)展的核心是一個(gè)用 C 和 編譯語言 寫的 Runtime 庫(kù)。它是 Objective-C 面向?qū)ο蠛蛣?dòng)態(tài)機(jī)制的基石。
Objective-C 是一個(gè)動(dòng)態(tài)語言,這意味著它不僅需要一個(gè)編譯器,也需要一個(gè)運(yùn)行時(shí)系統(tǒng)來動(dòng)態(tài)得創(chuàng)建類和對(duì)象、進(jìn)行消息傳遞和轉(zhuǎn)發(fā)。理解 Objective-C 的 Runtime 機(jī)制可以幫我們更好的了解這個(gè)語言,適當(dāng)?shù)臅r(shí)候還能對(duì)語言進(jìn)行擴(kuò)展,從系統(tǒng)層面解決項(xiàng)目中的一些設(shè)計(jì)或技術(shù)問題。了解 Runtime ,要先了解它的核心 - 消息傳遞 (Messaging)。
Runtime其實(shí)有兩個(gè)版本: modern和legacy。我們現(xiàn)在用的 Objective-C 2.0 采用的是現(xiàn)行 (Modern) 版的 Runtime 系統(tǒng),只能運(yùn)行在 iOS 和 macOS 10.5 之后的 64 位程序中。而 macOS 較老的32位程序仍采用 Objective-C 1 中的(早期)Legacy版本的 Runtime 系統(tǒng)。這兩個(gè)版本最大的區(qū)別在于當(dāng)你更改一個(gè)類的實(shí)例變量的布局時(shí),在早期版本中你需要重新編譯它的子類,而現(xiàn)行版就不需要。
Runtime 基本是用 C 和匯編寫的,可見蘋果為了動(dòng)態(tài)系統(tǒng)的高效而作出的努力。你可以在這里下到蘋果維護(hù)的開源代碼。蘋果和GNU各自維護(hù)一個(gè)開源的 runtime 版本,這兩個(gè)版本之間都在努力的保持一致。平時(shí)的業(yè)務(wù)中主要是使用官方Api,解決我們框架性的需求。
高級(jí)編程語言想要成為可執(zhí)行文件需要先編譯為匯編語言再匯編為機(jī)器語言,機(jī)器語言也是計(jì)算機(jī)能夠識(shí)別的唯一語言,但是OC并不能直接編譯為匯編語言,而是要先轉(zhuǎn)寫為純C語言再進(jìn)行編譯和匯編的操作,從OC到C語言的過渡就是由runtime來實(shí)現(xiàn)的。然而我們使用OC進(jìn)行面向?qū)ο箝_發(fā),而C語言更多的是面向過程開發(fā),這就需要將面向?qū)ο蟮念愞D(zhuǎn)變?yōu)槊嫦蜻^程的結(jié)構(gòu)體。
>數(shù)據(jù)結(jié)構(gòu)

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

Objective-C 語言 中,對(duì)象方法調(diào)用都是類似[receiver selector];的形式,其本質(zhì)就是讓對(duì)象在運(yùn)行時(shí)發(fā)送消息的過程。編譯器轉(zhuǎn)成消息發(fā)送objc_msgSend(receiver, selector)或objc_msgSend(recevier,selector,org1,org2,…),runtime時(shí)執(zhí)行的流程是這樣的:
- 通過
recevier的isa 指針找到recevier的Class(類); - 在
Class的cache(方法緩存)的散列表中尋找對(duì)應(yīng)的IMP(方法實(shí)現(xiàn)); - 如果在
cache中沒有找到對(duì)應(yīng)的IMP,就繼續(xù)在Class的method list(方法列表)中找對(duì)應(yīng)的selector,如果找到則填充到cache中,并返回selector; - 如果在
Class中沒有找到這個(gè)selector,就繼續(xù)在它的superClass(父類)中尋找; - 一旦找到對(duì)應(yīng)的
selector,直接執(zhí)行recevier對(duì)應(yīng)selector方法實(shí)現(xiàn)的IMP。 - 若找不到對(duì)應(yīng)的
selector,消息被轉(zhuǎn)發(fā)或者臨時(shí)向recevier添加這個(gè)selector對(duì)應(yīng)的實(shí)現(xiàn)方法,否則就會(huì)發(fā)生崩潰。
但這種實(shí)現(xiàn)有個(gè)問題,效率低。一個(gè)class往往只有20%的函數(shù)會(huì)被經(jīng)常調(diào)用,可能占總調(diào)用次數(shù)的80%。每個(gè)消息都需要遍歷一次objc_method_list并不合理。如果把經(jīng)常被調(diào)用的函數(shù)緩存下來,那可以大大提高函數(shù)查詢的效率。這也就是objc_class中另一個(gè)重要成員objc_cache做的事情 - 再找到selector之后,把selector的method_name作為key,method_imp作為value給存起來。當(dāng)再次收到recevier消息的時(shí)候,可以直接在cache里找到,避免去遍歷objc_method_list。從前面的源代碼可以看到objc_cache是存在objc_class結(jié)構(gòu)體中的。
objec_msgSend的方法定義如下:
OBJC_EXPORT id objc_msgSend(id self, SEL op, ...)
>Runtime 中的概念解析
-
類對(duì)象(objc_class)
struct objc_class {
// objc_class 結(jié)構(gòu)體的實(shí)例指針
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
// 指向父類的指針
Class _Nullable super_class OBJC2_UNAVAILABLE;
// 類的名字
const char * _Nonnull name OBJC2_UNAVAILABLE;
// 類的版本信息,默認(rèn)為 0
long version OBJC2_UNAVAILABLE;
// 類的信息,供運(yùn)行期使用的一些位標(biāo)識(shí)
long info OBJC2_UNAVAILABLE;
// 該類的實(shí)例變量大小;
long instance_size OBJC2_UNAVAILABLE;
// 該類的實(shí)例變量列表
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
// 方法定義的列表
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
// 方法緩存
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
// 遵守的協(xié)議列表
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
類對(duì)象就是一個(gè)結(jié)構(gòu)體struct objc_class,這個(gè)結(jié)構(gòu)體存放的數(shù)據(jù)稱為元數(shù)據(jù)(metadata),該結(jié)構(gòu)體的第一個(gè)成員變量也是isa指針,這就說明了Class本身其實(shí)也是一個(gè)對(duì)象,因此我們稱之為類對(duì)象,類對(duì)象在編譯期產(chǎn)生用于創(chuàng)建實(shí)例對(duì)象,是單例。
-
實(shí)例(objc_object)
Object(對(duì)象)被定義為objc_object結(jié)構(gòu)體,其數(shù)據(jù)結(jié)構(gòu)如下:
/// Represents an instance of a class.
struct objc_object {
// objc_object 結(jié)構(gòu)體的實(shí)例指針
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
/// 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 指針。換句話說,一個(gè)Object(對(duì)象)唯一保存的就是它所屬 Class(類)的地址。當(dāng)我們對(duì)一個(gè)對(duì)象,進(jìn)行方法調(diào)用時(shí),比如[receiver selector];,它會(huì)通過objc_object 結(jié)構(gòu)體的isa 指針去找對(duì)應(yīng)的object_class 結(jié)構(gòu)體,然后在object_class 結(jié)構(gòu)體的 methodLists(方法列表)中找到我們調(diào)用的方法,然后執(zhí)行。
-
元類(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 指針實(shí)際上指向的的是類對(duì)象自身的Meta Class(元類)。
Meta Class(元類)就是一個(gè)類對(duì)象所屬的類。一個(gè)對(duì)象所屬的類叫做類對(duì)象,而一個(gè)類對(duì)象所屬的類就叫做元類。
Runtime 中把類對(duì)象所屬類型就叫做
Meta Class(元類),用于描述類對(duì)象本身所具有的特征,而在元類的methodLists中,保存了類的方法鏈表,即所謂的類方法。并且類對(duì)象中的isa 指針指向的就是元類。每個(gè)類對(duì)象有且僅有一個(gè)與之相關(guān)的元類。
對(duì)象方法的調(diào)用過程,我們是通過對(duì)象的isa 指針找到對(duì)應(yīng)的 Class(類);然后在Class(類)的method list(方法列表)中找對(duì)應(yīng)的selector。而 類方法的調(diào)用過程 和對(duì)象方法調(diào)用差不多,流程如下:
- 通過
類對(duì)象的isa 指針找到所屬的Meta Class(元類); - 在
Meta Class(元類)的method list(方法列表)中找到對(duì)應(yīng)的selector; - 執(zhí)行對(duì)應(yīng)的
selector。
下面我們通過一張圖來清晰地表示出實(shí)例對(duì)象(Object)、類(Class)、Meta Class(元類)的指向關(guān)系。

通過上圖我們可以看出整個(gè)體系構(gòu)成了一個(gè)自閉環(huán),struct objc_object結(jié)構(gòu)體實(shí)例它的isa指針指向類對(duì)象,類對(duì)象的isa指針指向了元類,super_class指針指向了父類的類對(duì)象,而元類的super_class指針指向了父類的元類,那元類的isa指針又指向了自己。
元類(Meta Class)是一個(gè)類對(duì)象的類。
在上面我們提到,所有的類自身也是一個(gè)對(duì)象,我們可以向這個(gè)對(duì)象發(fā)送消息(即)。為了調(diào)用類方法,這個(gè)類的
isa指針必須指向一個(gè)包含這些類方法的一個(gè)objc_class結(jié)構(gòu)體。這就引出了meta-class的概念,元類中保存了創(chuàng)建類對(duì)象以及類方法所需的所有信息。任何NSObject繼承體系下的meta-class都使用NSObject的meta-class作為自己的所屬類,而基類的meta-class的isa指針是指向它自己。
@interface NSObject (Test)
+ (void)foo;
@end
@implementation NSObject (Test)
- (void) foo {
NSLog(@"%@",NSStringFromSelector(_cmd));
return;
}
@end
int main(int argc, char * argv[]) {
@autoreleasepool {
[NSObject foo];
[[NSObject new] foo];
}
return 0;
}
輸出結(jié)果:
foo
foo
解析:類的實(shí)例方法是存儲(chǔ)在類的
methodLists中,而類方法則是存儲(chǔ)在元類的methodLists中,NSObject的元類的superclass是指向Class,當(dāng)調(diào)用[NSObject foo]的時(shí)候,因?yàn)檫@是一個(gè)類方法調(diào)用,所以從元類中查找簽名為foo的方法,沒有發(fā)現(xiàn),然后再沿superclass繼續(xù)查找,結(jié)果在Class中查找到該方法,于是調(diào)用該方法輸出。但如果將NSObject的分類換成其他類的分類(如NSString),會(huì)發(fā)現(xiàn)程序崩潰,這是因?yàn)楹灻麨?code>foo的函數(shù)在NSString中,而當(dāng)我們進(jìn)行類方法調(diào)用的時(shí)候,最后會(huì)查找到NSObject的Class中,但該Class中并沒有對(duì)應(yīng)的方法簽名,于是再沿superclass向上查找,由于NSObject的superclass是nil,于是拋出unrecognized selector。
-
Method(objc_method)
object_class 結(jié)構(gòu)體 的methodLists(方法列表)中存放的元素就是Method(方法)。在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 OBJC2_UNAVAILABLE;
// 方法類型
char * _Nullable method_types OBJC2_UNAVAILABLE;
// 方法實(shí)現(xiàn)
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
objc_method 結(jié)構(gòu)體中包含了method_name(方法名),method_types(方法類型)和method_imp(方法實(shí)現(xiàn))。說明SEL和IMP其實(shí)都是Method的屬性。下面,我們來了解下這三個(gè)變量。
1. SEL(objc_selector)
Objc.h
/// An opaque type that represents a method selector.代表一個(gè)方法的不透明類型
typedef struct objc_selector *SEL;
objc_msgSend函數(shù)第二個(gè)參數(shù)類型為SEL,它是selector在Objective-C中的表示類型(Swift中是Selector類)。selector是方法選擇器,可以理解為區(qū)分方法的ID,而這個(gè)ID的數(shù)據(jù)結(jié)構(gòu)是SEL。SEL是一個(gè)指向objc_selector 結(jié)構(gòu)體的指針。
在runtime 相關(guān)頭文件中并沒有找到明確的定義。不過,通過測(cè)試我們可以得出:SEL只是一個(gè)保存方法名的字符串。
SEL sel = @selector(viewDidLoad);
NSLog(@"%s", sel); // 輸出:viewDidLoad
SEL sel1 = @selector(test);
NSLog(@"%s", sel1); // 輸出:test
@property SEL selector;
可以看到selector是SEL的一個(gè)實(shí)例。
A method selector is a C string that has been registered (or “mapped“) with the Objective-C runtime. Selectors generated by the compiler are automatically mapped by the runtime when the class is loaded.
其實(shí)selector就是個(gè)映射到方法的C字符串,你可以用Objective-C編譯器命令@selector()或者Runtime系統(tǒng)的sel_registerName函數(shù)來獲得一個(gè)SEL類型的方法選擇器。
selector既然是一個(gè)string,是類似className+method的組合,命名規(guī)則有兩條:
- 同一個(gè)類,selector不能重復(fù)
- 不同的類,selector可以重復(fù)
這也帶來了一個(gè)弊端,我們?cè)趯?code>C代碼的時(shí)候,經(jīng)常會(huì)用到函數(shù)重載,就是函數(shù)名相同,參數(shù)不同,但是這在Objective-C中是行不通的,因?yàn)?code>selector只記了method的name,沒有參數(shù),所以沒法區(qū)分不同的method。
比如:
- (void)caculate(NSInteger)num;
- (void)caculate(CGFloat)num;
是會(huì)報(bào)錯(cuò)的。
我們只能通過命名來區(qū)別:
- (void)caculateWithInt(NSInteger)num;
- (void)caculateWithFloat(CGFloat)num;
在不同類中相同名字的方法所對(duì)應(yīng)的方法選擇器是相同的,即使方法名字相同而變量類型不同也會(huì)導(dǎo)致它們具有相同的方法選擇器。
2. IMP(method_imp)
/// 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用來找到函數(shù)地址,然后執(zhí)行函數(shù)。在iOS的Runtime中,Method通過selector和IMP兩個(gè)屬性,實(shí)現(xiàn)了快速查詢方法及實(shí)現(xiàn),相對(duì)提高了性能,又保持了靈活性。
IMP和SEL關(guān)系:
每一個(gè)繼承于NSObject的類都能自動(dòng)獲得runtime的支持。在這樣的一個(gè)類中有一個(gè)isa指針指向該類定義的數(shù)據(jù)結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體是由編譯器編譯時(shí)為類(需繼承于NSObject)創(chuàng)建的。在這個(gè)結(jié)構(gòu)體中有包括了指向其父類類定義的指針以及 Dispatch table。Dispatch table是一張SEL和IMP的對(duì)應(yīng)表。也就是說方法編號(hào)SEL最后還是要通過Dispatch table表尋找到對(duì)應(yīng)的IMP,IMP就是一個(gè)函數(shù)指針,然后執(zhí)行這個(gè)方法:
1)通過方法獲得方法的編號(hào):SEL methodId=@selector(methodName);或者SEL methodId = NSSelectorFromString(methodName);
2)通過方法編號(hào)執(zhí)行該編號(hào)的方法:[self performSelector:methodId withObject:nil];
3)通過方法編號(hào)獲取該編號(hào)的方法名 NSString *methodName = NSStringFromSelector(methodId);
4)通過方法編號(hào)獲得IMP:IMP methodPoint = [self methodForSelector:methodId];
5)執(zhí)行IMP:void (*func)(id, SEL, id) = (void *)imp;、func(self, methodName,param);
注意:如果方法沒有傳入?yún)?shù)時(shí):
void (*func)(id, SEL) = (void *)imp;、func(self, methodName);。如果方法傳入一個(gè)參數(shù)時(shí):void (*func)(id, SEL,id) = (void *)imp;、func(self, methodName,param);。如果方法傳入倆個(gè)參數(shù)時(shí):void (*func)(id, SEL,id,id) = (void *)imp;、func(self, methodName,param1,param2);。
想更深入了解 IMP 的小伙伴請(qǐng)戳這里。
3. char *method_types
方法類型method_types是個(gè)字符串,用來存儲(chǔ)方法的參數(shù)類型和返回值類型。
-
類緩存(objc_cache)
當(dāng)Objective-C運(yùn)行時(shí)通過跟蹤它的isa 指針檢查對(duì)象時(shí),它可以找到一個(gè)實(shí)現(xiàn)許多方法的對(duì)象。然而,你可能只調(diào)用它們的一小部分,并且每次查找時(shí),搜索所有選擇器的類分派表沒有意義。所以類實(shí)現(xiàn)一個(gè)緩存,每當(dāng)你搜索一個(gè)類分派表,并找到相應(yīng)的選擇器,它把它放入它的緩存。所以當(dāng)objc_msgSend查找一個(gè)類的選擇器,它首先搜索類緩存。這是基于這樣的理論:如果你在類上調(diào)用一個(gè)消息,你可能以后再次調(diào)用該消息。
為了加速消息分發(fā), 系統(tǒng)會(huì)對(duì)方法和對(duì)應(yīng)的地址進(jìn)行緩存,就放在上述的objc_cache,所以在實(shí)際運(yùn)行中,大部分常用的方法都是會(huì)被緩存起來的,Runtime系統(tǒng)實(shí)際上非常快,接近直接執(zhí)行內(nèi)存地址的程序速度。
-
Runtime消息轉(zhuǎn)發(fā)
進(jìn)行一次發(fā)送消息會(huì)在相關(guān)的類對(duì)象中搜索方法列表,如果找不到則會(huì)沿著繼承樹向上一直搜索知道繼承樹根部(通常為NSObject),如果還是找不到并且消息轉(zhuǎn)發(fā)都失敗了就回執(zhí)行doesNotRecognizeSelector:方法報(bào)unrecognized selector錯(cuò)。那么消息轉(zhuǎn)發(fā)到底是什么呢?接下來將會(huì)逐一介紹最后的三次機(jī)會(huì)。
- 動(dòng)態(tài)方法解析(消息動(dòng)態(tài)解析)
- 備用接收者(消息接受者重定向)
- 完整消息轉(zhuǎn)發(fā)(消息重定向)

1.動(dòng)態(tài)方法解析(消息動(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)用。我們可以通過重寫這兩個(gè)方法,添加其他函數(shù)實(shí)現(xiàn),并返回YES, 那運(yùn)行時(shí)系統(tǒng)就會(huì)重新啟動(dòng)一次消息發(fā)送的過程。
主要用的的方法如下:
// 類方法未找到時(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)];
}
// 重寫 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é)果:
funMethod
從上邊的例子中,我們可以看出,雖然我們沒有實(shí)現(xiàn)fun方法,但是通過重寫resolveInstanceMethod:,利用class_addMethod方法添加對(duì)象方法實(shí)現(xiàn)funMethod方法并執(zhí)行。從打印結(jié)果來看,成功調(diào)起了funMethod 方法。
我們注意到 class_addMethod 方法中的特殊參數(shù)
v@:,具體可參考官方文檔中關(guān)于Type Encodings的說明:傳送門
2.備用接收者(消息接受者重定向)
如果上一步中+resolveInstanceMethod:或者+resolveClassMethod:沒有添加其他函數(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:無論是返回YES還是NO,只要其中沒有添加其他函數(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
@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é)果:
fun
可以看到,雖然當(dāng)前ViewController沒有實(shí)現(xiàn)fun方法,+resolveInstanceMethod:也沒有添加其他函數(shù)實(shí)現(xiàn)。但是我們通過forwardingTargetForSelector把當(dāng)前ViewController的方法轉(zhuǎn)發(fā)給了Person對(duì)象去執(zhí)行了。打印結(jié)果也證明我們成功實(shí)現(xiàn)了轉(zhuǎn)發(fā)。
我們通過forwardingTargetForSelector可以修改消息的接收者,該方法返回參數(shù)是一個(gè)對(duì)象,如果這個(gè)對(duì)象是不是nil,也不是self,系統(tǒng)會(huì)將運(yùn)行的消息轉(zhuǎn)發(fā)給這個(gè)對(duì)象執(zhí)行。否則,繼續(xù)進(jìn)行下一步——消息重定向流程。
3.完整消息轉(zhuǎn)發(fā)(消息重定向)
如果經(jīng)過消息動(dòng)態(tài)解析、消息接受者重定向,Runtime系統(tǒng)還是找不到相應(yīng)的方法實(shí)現(xiàn)而無法響應(yīng)消息,Runtime系統(tǒng)會(huì)利用-methodSignatureForSelector:或者 +methodSignatureForSelector:方法獲取函數(shù)的參數(shù)和返回值類型。
如果methodSignatureForSelector:返回了一個(gè) NSMethodSignature 對(duì)象(函數(shù)簽名),Runtime系統(tǒng)就會(huì)創(chuàng)建一個(gè)NSInvocation對(duì)象,并通過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
@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]; // 若仍然無法響應(yīng),則報(bào)錯(cuò):找不到響應(yīng)方法
}
}
@end
輸出結(jié)果:
fun
可以看到,我們?cè)?code>-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è)流程。
參考鏈接:
http://www.itdecent.cn/p/6ebda3cd8052
http://www.itdecent.cn/p/633e5d8386a8
https://www.imooc.com/article/38310
官方文檔:
runtime源碼地址(開源)
蘋果官方Runtime編程指南
objc_msgSend
objc_msgSend_fpret
objc_msgsend_stret
objc_msgsendsuper
objc_msgsendsuper_stret
大神入口:
http://yulingtianxia.com/blog/2016/06/15/Objective-C-Message-Sending-and-Forwarding/