什么是runtime?
OC是一門動態(tài)語言,與C++這種靜態(tài)語言不通,驚天語言的各種數(shù)據(jù)結(jié)構(gòu)在編譯期就已經(jīng)決定了,不能夠被修改。而動態(tài)語言卻可以使我們再程序運行期,動態(tài)的修改一個類的結(jié)構(gòu),如修改方法實現(xiàn)沒綁定實例變量等。
OC作為動態(tài)語言,它總會想辦法將靜態(tài)語言在編譯器決定的事情,推遲到運行期來做。所以,僅有編譯器是不夠的,它還需要一個運行時系統(tǒng)(runtime system),這也就是OC的runtime系統(tǒng)的意義,它是OC運行框架的基石。
與runtime交互
我們的OC語言是離不開runtime的。我們會在三個層次上和runtime進(jìn)行交互,分別是:OC源碼,通過Foundation框架定義的Nsobject方法,直接調(diào)用runtime提供的接口方法。
- OC源碼:大多數(shù)情況下,我們僅使用OC語言來編寫代碼,如NSObject,類屬性,中括號的方法調(diào)用,協(xié)議,分類等。而這一切的背后,都是由runtime來支持的。我們平常所熟知的各種類型,背后都有runtime對應(yīng)的C語言結(jié)構(gòu)體,及C和匯編實現(xiàn)。
- NSObject: Cocoa中大部分類均繼承于NSObject,因此大多數(shù)類都繼承了NSObject所提供的方法。在NSObject中,有若干方法是運行時動態(tài)決定結(jié)果的,這背后其實是runtime系統(tǒng)對應(yīng)數(shù)據(jù)結(jié)構(gòu)的支持。如isKindOfClass和isMemberOfClass 檢查類是否屬于指定的Class的繼承體系中;responderToSelector 檢查對象是否能響應(yīng)指定的消息;conformsToProtocol 檢查對象是否遵循某個協(xié)議;methodForSelector返回指定方法實現(xiàn)的地址。
- Runtime函數(shù):Runtime 系統(tǒng)是一個由一系列函數(shù)和數(shù)據(jù)結(jié)構(gòu)組成,具有公共接口的動態(tài)共享庫。頭文件存放于/usr/include/objc目錄下。許多函數(shù)允許你用純C代碼來重復(fù)實現(xiàn) Objc 中同樣的功能。雖然有一些方法構(gòu)成了NSObject類的基礎(chǔ),但是你在寫 Objc 代碼時一般不會直接用到這些函數(shù)的,除非是寫一些 Objc 與其他語言的橋接或是底層的debug工作。在Objective-C
- Runtime Reference 中有對 Runtime 函數(shù)的詳細(xì)文檔。
所謂的runtime黑魔法,只是基于OC各種底層數(shù)據(jù)結(jié)構(gòu)上的應(yīng)用。
因此,要想了解runtime,就要先了解runtime中定義的各種數(shù)據(jù)結(jié)構(gòu)。我們先從最基礎(chǔ)的objc_object和objc_class開始。
從NSObject說起
我們知道,在OC中,基本上所有的類的基類,都是NSObject。因此要深入了解OC中的類的結(jié)構(gòu),就要從NSObject這個類說起。
在XCode中,我們可以通過查看定義來了解NSObject的實現(xiàn):
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
NSObject 僅有一個實例變量Class isa:
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
Class實質(zhì)上是指向objc_class的指針。而objc_class的定義又是如何呢,在XCode中,我們繼續(xù)查看定義:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
OK,到這里,對OC中類的結(jié)構(gòu)的探索,我們先暫停一下。網(wǎng)上可以搜到很多博客,都是按照上面objc_class的定義來繼續(xù)講解的。
BUT!??!
難道都沒有看到OBJC2_UNAVAILABLE這個提示嗎???在OC 2.0中,這種關(guān)于objc_class的定義已經(jīng)廢棄掉了??!許多博客都是在根本沒有深入了解的情況下,就開始人云亦云,其實自己未必知道自己在說什么。這樣做是很不負(fù)責(zé)任的,結(jié)果往往是誤人子弟,自己不明白,還把別人帶到了坑里。
吐槽完畢,我們繼續(xù)來探索OC類的定義。前面說到,關(guān)于objc_class的定義,我們在XCode里面是看不到其真實的定義了,那么到哪里繼續(xù)深入呢?看runtime源碼。
在runtime源碼的objc-runtime-new.h中,可以看到objc_class在OC 2.0中的定義。
objc_class
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
// 省略其他方法
。。。
}
可以看到,objc_class繼承自objc_object, 即在runtime中,class也被看做一種對象。在objc_class中,有三個數(shù)據(jù)成員:
1、 Class superclass:同樣是Class類型,表明當(dāng)前類的父類。
2、cache_t cache:cache用于優(yōu)化方法調(diào)用,其對應(yīng)的數(shù)據(jù)結(jié)構(gòu)如是:
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
// 省略其余方法
。。。
}
typedef uintptr_t cache_key_t;
struct bucket_t {
private:
cache_key_t _key;
IMP _imp;
public:
inline cache_key_t key() const { return _key; }
inline IMP imp() const { return (IMP)_imp; }
inline void setKey(cache_key_t newKey) { _key = newKey; }
inline void setImp(IMP newImp) { _imp = newImp; }
void set(cache_key_t newKey, IMP newImp);
cache的核心是有一個類型為bucket_t的指針,它指向了一個以_key和IMP對應(yīng)的緩存節(jié)點。
這里我們第一次遇到uintptr_t類型(_key) 。在runtime中,uintptr_t定義為
#ifndef _UINTPTR_T
#define _UINTPTR_T
typedef unsigned long uintptr_t;
#endif /* _UINTPTR_T */
可以理解為void *。
runtime方法調(diào)用的流程是,當(dāng)要調(diào)用一個方法時,先不去Class的方法列表中查找,而是先去找cache_t cache 。當(dāng)系統(tǒng)調(diào)用過一個方法后,會將其實現(xiàn)IMP和key存放到cache中,因為理論上一個方法調(diào)用過后,被再次調(diào)用的概率很大。關(guān)于方法調(diào)用,我們將會在別的章節(jié)描述。
3、 class_data_bits_t bits:這是Class的核心,其本質(zhì)是一個可以被Mask的指針類型。根據(jù)不同的Mask,可以取出不同的值。
struct class_data_bits_t {
// Values are the FAST_ flags above.
uintptr_t bits;
public:
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
void setData(class_rw_t *newData)
{
assert(!data() || (newData->flags & (RW_REALIZING | RW_FUTURE)));
// Set during realization or construction only. No locking needed.
// Use a store-release fence because there may be concurrent
// readers of data and data's contents.
uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
atomic_thread_fence(memory_order_release);
bits = newBits;
}
class_data_bits_t bits 僅含有一個成員uintptr_t bits, 可以理解為一個‘復(fù)合指針’。什么意思呢,就是bits不僅包含了指針,同時包含了Class的各種異或flag,來說明Class的屬性。把這些信息復(fù)合在一起,僅用一個uint指針bits來表示。當(dāng)需要取出這些信息時,需要用對應(yīng)的以FAST_ 前綴開頭的flag掩碼對bits做按位與操作。
例如,我們需要取出Classs的核心信息class_rw_t, 則需要調(diào)用方法:
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
該方法返回一個class_rw_t* ,需要對bits 進(jìn)行FAST_DATA_MASK 的與操作。
讓我們再看一下Class 的核心結(jié)構(gòu)class_rw_t:
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro; // 類不可修改的原始核心
// 下面三個array,method,property, protocol,可以被runtime 擴展,如Category
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
// 和繼承相關(guān)的東西
Class firstSubclass;
Class nextSiblingClass;
// Class對應(yīng)的 符號名稱
char *demangledName;
// 以下方法省略
...
}
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
method_list_t *baseMethods() const {
* return baseMethodList;
}
};
可以看到,在class_ro_t 中包含了類的名稱,以及method_list_t, protocol_list_t, ivar_list_t, property_list_t 這些類的基本信息。 在class_ro_t 的信息是不可修改和擴展的。
而在更外一層 class_rw_t 中,有三個數(shù)組method_array_t, property_array_t, protocol_array_t:
這三個數(shù)組是可以被runtime動態(tài)擴展的。
objc_class 中包含class_data_bits_t, class_data_bits_t 中通過FAST_DATA_MASK獲取指向class_rw_t類型的指針,而在class_rw_t中包含class_ro_t,類的核心const信息。
realizeClass
在objc_class的data()方法最初返回的是const class_ro_t * 類型,也就是類的基本信息。因為在調(diào)用realizeClass方法前,Category定義的各種方法,屬性還沒有附加到class上,因此只能夠返回類的基本信息。
而當(dāng)我們調(diào)用realizeClass時,會在函數(shù)內(nèi)部將Category中定義的各種擴展附加到class上,同時改寫data()的返回值為class_rw_t *類型,核心代碼如下:
const class_ro_t *ro;
class_rw_t *rw;
ro = (const class_ro_t *)cls->data();
if (ro->flags & RO_FUTURE) {
// This was a future class. rw data is already allocated.
rw = cls->data();
ro = cls->data()->ro;
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// Normal class. Allocate writeable class data.
rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
rw->ro = ro;
rw->flags = RW_REALIZED|RW_REALIZING;
cls->setData(rw);
}
得出結(jié)論,在class沒有調(diào)用realizeClass之前,不是真正完整的類。
objc_object
OC的底層實現(xiàn)是runtime,在runtime這一層,對象被定義為objc_object結(jié)構(gòu)體,類被定義為了objc_class結(jié)構(gòu)體。而objc_class繼承于objc_object, 因此,類可以看做是一類特殊的對象。
現(xiàn)在就來看objc_object是如何定義的:
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
// 省略其余方法
...
}
可以看到,objc_object的定義很簡單,僅包含一個isa_t類型。
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
// 省略其余
。。。
}
isa_t 是一個聯(lián)合,可以表示Class cls或uintptr_t bits類型。實際上在OC 2.0里面,多數(shù)時間用的是uintptr_t bits。bits是一個64位的數(shù)據(jù),每一位或幾位都表示了關(guān)于當(dāng)前對象的信息。具體的細(xì)節(jié),我們將會在Objective-C runtime機制(5)——iOS 內(nèi)存管理中作出描述。在這里我們先了解isa_t是一個聯(lián)合類型就好。
objc_object & objc_class
如果我們再回頭看一下objc_object和objc_class的定義,可以發(fā)現(xiàn)object和class是你中有我,我中有你的:
struct objc_object {
private:
isa_t isa; // unit聯(lián)合,可以表示Class類型,表明Object所屬的類
。。。
}
struct objc_class : objc_object { // objc_class繼承自objc_object,表明objc_class也是一個objc_object
Class superclass; // super class 是一個objc_class * 指針
。。。
}
如果用UML圖表示的話:

可以看到,objc_class也是一個objc_object類型,這意味著,objc_class中也有一個屬性isa,而這個isa,可以表示當(dāng)前類屬于(注意不是繼承)哪個類。而這種說明類是屬于哪個類的類,我們稱之為元類(meta-class)。
這里再重申一遍,元類不是類的父類。至于元類的用途,我們將會在OC的消息轉(zhuǎn)發(fā)中詳細(xì)講解?,F(xiàn)在只需要知道,每一個類都有一個與其對應(yīng)的元類。
總結(jié)
在本章中,我們從NSObject的定義出發(fā),了解了OC中類和對象所對應(yīng)的數(shù)據(jù)結(jié)構(gòu)objc_class和objc_object。關(guān)于NSObject,objc_class和objc_object三者之間的關(guān)系,我們可以用下面的圖來更清晰的了解:
