閱讀本文將了解下面幾個問題:
- 什么是 Runtime?
- 消息機制的基本原理
- Runtime中的數(shù)據(jù)結(jié)構(gòu)
- Runtime消息發(fā)送原理
- 消息發(fā)送流程總結(jié)
1 什么是 Runtime?
C/C++ 是靜態(tài)語言的代表,它們在編譯階段就已經(jīng)確定好了要調(diào)用的函數(shù),以及函數(shù)的實現(xiàn),如果函數(shù)未實現(xiàn)就會編譯報錯。
Objective-C 是一個動態(tài)語言,在編譯階段并不知道真正調(diào)用的是哪個函數(shù)。只有在運行時才知道具體會發(fā)生什么。
Objective-C 的動態(tài)性,決定了它不僅需要一個編譯器,也需要一個運行時系統(tǒng)來動態(tài)得創(chuàng)建類和對象、進行消息傳遞和轉(zhuǎn)發(fā)。這個運行時系統(tǒng)就是Runtime,它基本上是用C和匯編寫的一套API,這個庫使C語言有了面向?qū)ο蟮哪芰Α?/p>
2 消息機制的基本原理
OC的方法調(diào)用都是類似[receiver selector]的形式,其實每次都是一個運行時消息發(fā)送過程。
編譯階段:
[receiver selector]方法被編譯器轉(zhuǎn)化:
1.objc_msgSend(receiver,selector)(不帶參數(shù))
2.objc_msgSend(recevier,selector,org1,org2,…)(帶參數(shù))運行時階段:消息接收者
recever尋找對應(yīng)的selector:
1.recever能找到對應(yīng)的selector,直接執(zhí)行接收receiver對象的selector方法。
2.recever找不到對應(yīng)的selector,消息被轉(zhuǎn)發(fā)或者臨時向recever添加這個selector對應(yīng)的實現(xiàn)內(nèi)容,否則崩潰。
OC調(diào)用方法[receiver selector],編譯階段確定了要向哪個接收者發(fā)送message消息,但是接收者如何響應(yīng)決定于運行時的判斷。
Objective-C代碼在編譯和運行階段會被轉(zhuǎn)化為Runtime方式運行,Objective-C類、對象和方法等都對應(yīng)了C中的結(jié)構(gòu)體,我們來看一下它們是如何定義的。
3 Runtime中的數(shù)據(jù)結(jié)構(gòu)
Runtime代碼如何查看呢,我們可以通過下面的方式:
1.導(dǎo)入下面的頭文件,然后Command +鼠標右鍵點擊,即可進入Runtime的源碼文件。
#import <objc/runtime.h>
2.我們也可以通過組合鍵 [Cmd + Shift + O ] ,搜索相應(yīng)的文件進入。
下面我們就分析一下Objective-C代碼在C中對應(yīng)的結(jié)構(gòu)。
3.1 objc_msgSend
上面也提到過,Objective-C方法調(diào)用在編譯時都會轉(zhuǎn)化為對應(yīng)C函數(shù)的調(diào)用:objc_msgSend(receiver,selector)。
3.2 Object(實例)
objc/objc.h中,我們來看一下Object(對象),是如何定義的:
/// 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是一種通用的對象類型,它可以指向?qū)儆谌魏晤惖膶ο?。在這里id被定義為一個指向 objc_object結(jié)構(gòu)體的指針。
而objc_object結(jié)構(gòu)體只包含一個Class類型的isa 指針,也就是說,一個Object(對象)唯一保存的就是它所屬Class(類) 的地址。下面我們看一下 Class是如何定義的。
3.3 Class(類)
在objc/objc.h中,可以看到Class是一個指向objc_class結(jié)構(gòu)體的指針:
typedef struct objc_class *Class;
在objc/runtime.h中,是objc_class結(jié)構(gòu)體的具體定義:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class // 指向父類的指針;
const char * _Nonnull name // 類名;
long version // 類的版本信息,默認為 0;
long info // 類的信息,供運行期使用的一些位標識;
long instance_size // 該類的實例變量大小;
struct objc_ivar_list * _Nullable ivars // 該類的實例變量列表;
struct objc_method_list * _Nullable * _Nullable methodLists // 方法定義列表 ;
struct objc_cache * _Nonnull cache // 方法緩存;
struct objc_protocol_list * _Nullable protocols // 遵守的協(xié)議列表;
#endif
} OBJC2_UNAVAILABLE;
我們可以看到objc_class結(jié)構(gòu)體中定義了很多的成員變量:指向父類的指針、類的名字、版本、實例大小、實例變量列表、方法列表、緩存、遵守的協(xié)議列表等。這個結(jié)構(gòu)體存放的數(shù)據(jù)稱為元數(shù)據(jù)(metadata)。
我們還能注意到objc_class結(jié)構(gòu)體中也有一個isa指針。這就說明了Class本身其實也是一個對象,因此我們稱之為類對象,類對象在編譯期產(chǎn)生,用于創(chuàng)建實例對象,是單例。
3.4 元類(Meta Class):
我們可以發(fā)現(xiàn)實例對象和類對象結(jié)構(gòu)體中都擁有一個isa指針,實例對象的isa指針指向他所屬的類(Class),那么類對象的isa指針指向哪兒里呢?
類對象的isa指針指向了元類,元類(Meta Class)是一個類對象的類。在上面我們提到,所有的類自身也是一個對象,我們可以向這個對象發(fā)送消息(即調(diào)用類方法)。
為了調(diào)用類方法,這個類的isa指針必須指向一個包含這些類方法的一個objc_class結(jié)構(gòu)體。這就引出了meta-class的概念,元類中保存了創(chuàng)建類對象以及類方法所需的所有信息。
3.5 實例對象、類、元類之間的關(guān)系
上面,我們已經(jīng)了解了 實例對象(Object)、類(Class)、Meta Class(元類) 的基本概念。
下面,我們通過一張圖,來清晰的了解下它們之間的繼承關(guān)系,以及isa的指向關(guān)系:

isa指針指向:
- 實例對象的
isa指針指向了對應(yīng)的類對象,而類對象的isa指針指向了對應(yīng)的元類。 - 所有元類的isa指針最終指向了
NSObject元類,因此NSObject元類也被稱為根元類。根元類的isa指針又指向了自己。
super_class指針指向:
- 類對象的
super_class指針指向了父類對象,父類對象又指向了根類對象,根類對象最終指向了nil。 - 元類的
super_class指針指向了父元類。父元類又指向了根元類。而根元類的super_class指針指向了根類對象,最終指向了nil。
3.6 Method(方法)
object_class中methodLists(方法列表)存放的元素就是Method(方法)。
在objc/runtime.h中,看下定義:
/// An opaque type that represents a method in a class definition.
typedef struct objc_method *Method;
struct objc_method {
SEL _Nonnull method_name // 方法名;
char * _Nullable method_types // 方法類型;
IMP _Nonnull method_imp // 方法實現(xiàn);
}
下面,我們來了解下它的三個成員變量。
1. SEL(方法名)
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
SEL是一個指向objc_selector結(jié)構(gòu)體的指針,然而我們并不能在Runtime中找到它的結(jié)構(gòu)體的詳細定義。Objective-C在編譯時,會依據(jù)每一個方法的名字、參數(shù)序列,生成一個唯一的整型標識(Int類型的地址),這個標識就是SEL。
2. IMP(方法實現(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的實質(zhì)是一個函數(shù)指針,它指向了方法實現(xiàn)的首地址。IMP用來找到函數(shù)地址,然后執(zhí)行函數(shù)。
3. char * method_types (方法類型)
方法類型method_types是個字符串,用來存儲方法的參數(shù)類型和返回值類型。
到這里,Method的結(jié)構(gòu)就已經(jīng)很清楚了,Method將SEL(方法名) 和IMP(函數(shù)指針)關(guān)聯(lián)起來,當(dāng)對一個對象發(fā)送消息時,會通過給出的SEL(方法名)去找到IMP(函數(shù)指針),然后執(zhí)行。
3.7 類緩存(objc_cache)
objc_cache用于緩存最近使用的方法。一個類只有一部分方法是常用的,每次調(diào)用一個方法之后,這個方法就被緩存到objc_cache中,下次調(diào)用時runtime會先在objc_cache中查找,如果objc_cache中沒有,才會去methodList中查找。相比直接在類的方法列表中遍歷查找,效率更高。
4 深入理解Rutime消息發(fā)送
我們在分析了OC語言對應(yīng)的底層C結(jié)構(gòu)之后,現(xiàn)在可以進一步理解運行時的消息發(fā)送機制。先前講到,OC調(diào)用方法被編譯轉(zhuǎn)化為如下的形式:
id _Nullable objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
最后一步中我們提到:若找不到對應(yīng)的selector,消息被轉(zhuǎn)發(fā)或者臨時向recevier添加這個selector應(yīng)的實現(xiàn)方法,否則就會發(fā)生崩潰。
當(dāng)一個方法找不到的時候,Runtime提供了 消息動態(tài)解析、消息接受者重定向、消息重定向 三種方法來處理,這三種方法的調(diào)用關(guān)系如下圖:

4.1 動態(tài)方法解析(Dynamic Method Resolution)
所謂動態(tài)解析,我們可以理解為通過cache和方法列表沒有找到方法時,Runtime為我們提供一次動態(tài)添加方法實現(xiàn)的機會,主要用到的方法如下:
//OC方法:
//類方法未找到時調(diào)起,可于此添加類方法實現(xiàn)
+ (BOOL)resolveClassMethod:(SEL)sel
//實例方法未找到時調(diào)起,可于此添加實例方法實現(xiàn)
+ (BOOL)resolveInstanceMethod:(SEL)sel
//Runtime方法:
/**
運行時方法:向指定類中添加特定方法實現(xiàn)的操作
@param cls 被添加方法的類
@param name selector方法名
@param imp 指向?qū)崿F(xiàn)方法的函數(shù)指針
@param types imp函數(shù)實現(xiàn)的返回值與參數(shù)類型
@return 添加方法是否成功
*/
BOOL class_addMethod(Class _Nullable cls,
SEL _Nonnull name,
IMP _Nonnull imp,
const char * _Nullable types)
下面使用一個示例來說明動態(tài)解析:ViewController類中聲明方法卻未添加實現(xiàn),我們通過Runtime動態(tài)方法解析的操作為其添加方法實現(xiàn),具體代碼如下:
#import "ViewController.h"
#import <objc/runtime.h>
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[[self class] performSelector:@selector(run)];
[self performSelector:@selector(walk)];
}
//重寫父類方法:處理類方法
+ (BOOL)resolveClassMethod:(SEL)sel {
if(sel == @selector(run)){
class_addMethod(object_getClass(self), sel, class_getMethodImplementation(object_getClass(self), @selector(ssl_run)), "v@");
return YES; //添加函數(shù)實現(xiàn),返回YES
}
return [class_getSuperclass(self) resolveClassMethod:sel];
}
//重寫父類方法:處理實例方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if(sel == @selector(walk)){
class_addMethod([self class], sel, class_getMethodImplementation([self class], @selector(ssl_walk)), "v@");
return YES;
}
return [super resolveInstanceMethod:sel];
}
+ (void)ssl_run {
NSLog(@"%s ",__func__);
}
- (void)ssl_walk {
NSLog(@"%s ",__func__);
}
運行程序,代碼沒有崩潰,并打印如下結(jié)果:
+[ViewController ssl_run]
-[ViewController ssl_walk]
class_addMethod 方法中的特殊參數(shù)“v@”,具體可參考官方文檔中關(guān)于 Type Encodings 的說明:點擊查看
這里+resolveInstanceMethod:或者 +resolveClassMethod:無論是返回YES,還是返回NO,只要其中沒有添加其他函數(shù)實現(xiàn),Runtime都會進行下一步:消息接受者重定向。
4.2 消息接收者重定向
這一步會調(diào)用下面兩個方法:
//重定向類方法的消息接收者,返回一個類
+ (id)forwardingTargetForSelector:(SEL)aSelector
//重定向?qū)嵗椒ǖ南⒔邮苷?,返回一個實例對象
- (id)forwardingTargetForSelector:(SEL)aSelector
如果當(dāng)前對象實現(xiàn)了這兩個方法,Runtime就會調(diào)用這兩個方法,允許我們將消息的接受者轉(zhuǎn)發(fā)給其他對象。
下面使用一個示例來說明消息接收者的重定向:
#import "ViewController.h"
#import <objc/runtime.h>
@interface Person : NSObject
@end
@implementation Person
+ (void)run {
NSLog(@"%s ",__func__);
}
- (void)walk {
NSLog(@"%s ",__func__);
}
@end
@interface ViewController ()
@property (nonatomic, strong) Person *person;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[[self class] performSelector:@selector(run)];
[self performSelector:@selector(walk)];
}
//重定向類方法:返回一個類對象
+ (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(run)) {
return [Person class];
}
return [super forwardingTargetForSelector:aSelector];
}
//重定向?qū)嵗椒ǎ悍祷仡惖膶嵗?- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(walk)) {
return self.person;
}
return [super forwardingTargetForSelector:aSelector];
}
- (Person *)person {
if (!_person) {
_person = [Person new];
}
return _person;
}
@end
代碼沒有崩潰,并打印如下結(jié)果:
+[ViewController ssl_run]
-[ViewController ssl_walk]
動態(tài)方法解析階段無效時,我們可以通過forwardingTargetForSelector修改消息的接收者,該方法返回參數(shù)是一個對象,如果這個對象是非nil,非self,系統(tǒng)會將運行的消息轉(zhuǎn)發(fā)給這個對象執(zhí)行。否則,進行下一步:消息重定向。
4.3 消息重定向
這一步中首先會發(fā)送-methodSignatureForSelector:消息獲得函數(shù)的參數(shù)和返回值類型。如果-methodSignatureForSelector:返回nil ,Runtime則會發(fā)出-doesNotRecognizeSelector: 消息,程序這時也就掛掉了。如果返回了一個函數(shù)簽名,Runtime就會創(chuàng)建一個NSInvocation對象并發(fā)送 -forwardInvocation:消息給目標對象。
看下方法的定義:
// 獲取類方法函數(shù)的參數(shù)和返回值類型,返回簽名
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
// 類方法消息重定向
+ (void)forwardInvocation:(NSInvocation *)anInvocation;
// 獲取對象方法函數(shù)的參數(shù)和返回值類型,返回簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
// 對象方法消息重定向
- (void)forwardInvocation:(NSInvocation *)anInvocation;
舉個例子:
#import "ViewController.h"
#import <objc/runtime.h>
@interface Person : NSObject
@end
@implementation Person
- (void)walk {
NSLog(@"%s ",__func__);
}
@end
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 執(zhí)行walk函數(shù)
[self performSelector:@selector(walk)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return YES;// 返回YES,進入下一步轉(zhuǎn)發(fā)
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
return nil;// 返回nil,進入下一步轉(zhuǎn)發(fā)
}
// 獲取函數(shù)的參數(shù)和返回值類型,返回簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if ([NSStringFromSelector(aSelector) isEqualToString:@"walk"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];// 簽名,進入forwardInvocation
}
return [super methodSignatureForSelector:aSelector];
}
// 消息重定向
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL sel = anInvocation.selector;// 從anInvocation中獲取消息
Person *p = [Person new];
if([p respondsToSelector:sel]) {
[anInvocation invokeWithTarget:p];// 將消息轉(zhuǎn)發(fā)給其他對象處理
}
else {
[self doesNotRecognizeSelector:sel];// 報錯,代碼崩潰
}
}
@end
代碼沒有崩潰,并打印如下結(jié)果:
-[Person walk]
這一步中,通過簽名,Runtime生成了一個對象anInvocation,發(fā)送給了forwardInvocation,我們在forwardInvocation方法里面讓Person對象去執(zhí)行了walk函數(shù)。
以上就是Runtime的三次轉(zhuǎn)發(fā)流程。
5 消息發(fā)送流程總結(jié)
調(diào)用[receiver selector]后,進行的流程:
-
編譯階段:
[receiver selector]方法被編譯器轉(zhuǎn)化:-
objc_msgSend(receiver,selector)(不帶參數(shù))。 -
objc_msgSend(recevier,selector,org1,org2,…)(帶參數(shù))。
-
-
運行時階段:
recevier尋找對應(yīng)的selector:- 通過
recevier的isa指針找到recevier的class(類)。 - 在
Class(類)的cache(方法緩存)中尋找對應(yīng)的selector。 - 如果在
cache(方法緩存)中沒有找到對應(yīng)的selector,就繼續(xù)在Class(類)的methodList(方法列表)中查找,如果找到,緩存到cache中,并返回selector。 - 如果在
class(類)中沒有找到這個selector,就繼續(xù)在它的superclass(父類)中尋找。 - 一旦找到
selector,直接執(zhí)行相關(guān)聯(lián)的IMP(方法實現(xiàn))。 - 若找不到對應(yīng)的
selector,Runtime系統(tǒng)進入消息轉(zhuǎn)發(fā)階段。
- 通過
-
消息轉(zhuǎn)發(fā)階段:
- 動態(tài)解析:通過重寫
+resolveInstanceMethod:或者+resolveClassMethod:方法,利用class_addMethod方法添加其他函數(shù)實現(xiàn)。 - 消息接受者重定向:如果上一步?jīng)]有添加其他函數(shù)實現(xiàn),可在當(dāng)前對象中利用
forwardingTargetForSelector:方法將消息的接受者轉(zhuǎn)發(fā)給其他對象。 - 消息重定向:如果上一步返回值為
nil,則利用methodSignatureForSelector:方法獲取函數(shù)的參數(shù)和返回值類型。- 如果
methodSignatureForSelector:返回nil。則Runtime系統(tǒng)會發(fā)出doesNotRecognizeSelector:消息,程序也就崩潰了。 - 如果
methodSignatureForSelector:返回了一個函數(shù)簽名,Runtime就會創(chuàng)建一個NSInvocation對象并發(fā)送-forwardInvocation:消息給目標對象。
- 如果
- 動態(tài)解析:通過重寫