Objective-C runtime機制(1)——基本資料結構:objc_object & objc_class
前言
從本篇文章開始,就進入runtime的正篇。
什麼是runtime?
OC是一門動態語言,與C++這種靜態語言不同,靜態語言的各種資料結構在編譯期已經決定了,不能夠被修改。而動態語言卻可以使我們在程式執行期,動態的修改一個類的結構,如修改方法實現,繫結例項變數等。
OC作為動態語言,它總會想辦法將靜態語言在編譯期決定的事情,推遲到執行期來做。所以,僅有編譯器是不夠的,它需要一個執行時系統(runtime system),這也就是OC的runtime系統的意義,它是OC執行框架的基石。
與Runtime互動
我們的OC語言是離不開runtime的。我們會在三個層次上和runtime進行互動,分別是:OC原始碼,通過Foundation框架定義的NSObject方法,直接呼叫runtime提供的介面方法。
- OC原始碼:大多數情況下,我們僅使用OC語言來編寫程式碼,如NSObject,類屬性,中括號的方法呼叫,協議,分類等。而這一切的背後,都是由runtime來支援的。我們平常所熟知的各種型別,背後都有runtime對應的C語言結構體,及C和彙編實現。
- NSObject: Cocoa中大部分類均繼承於NSObject,因此大多數類都繼承了NSObject所提供的方法。在NSObject中,有若干方法是執行時動態決定結果的,這背後其實是runtime系統對應資料結構的支援。如
isKindOfClass
和isMemberOfClass
檢查類是否屬於指定的Class的繼承體系中;responderToSelector
methodForSelector
返回指定方法實現的地址。 - Runtime函式:Runtime 系統是一個由一系列函式和資料結構組成,具有公共介面的動態共享庫。標頭檔案存放於
/usr/include/objc
目錄下。許多函式允許你用純C程式碼來重複實現 Objc 中同樣的功能。雖然有一些方法構成了NSObject類的基礎,但是你在寫 Objc 程式碼時一般不會直接用到這些函式的,除非是寫一些 Objc 與其他語言的橋接或是底層的debug工作。在Objective-C Runtime Reference
就如在我們在前傳篇中提到的,所謂的runtime黑魔法,只是基於OC各種底層資料結構上的應用。
因此,要想了解runtime,就要先了解runtime中定義的各種資料結構。我們先從最基礎的objc_object和objc_class開始。
objc_object
OC的底層實現是runtime,在runtime這一層,物件被定義為objc_object
結構體,類被定義為了objc_class
結構體。
我們先看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
是一個聯合,可以表示多種型別,但是我們這裡僅關注Class cls
,它表明了物件屬於哪個類。關於isa_t
可以表示的其他型別,我們會在其他章節中描述。
objc_class
isa_t
中Class
型別其實是 typedef struct objc_class *Class
一個指標,指向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
中,有三個資料成員:
Class superclass
:同樣是Class型別,表明當前類的父類。
cache_t cache
:cache用於優化方法呼叫,其對應的資料結構如是:
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
對應的快取節點。
runtime方法呼叫的流程是,當要呼叫一個方法時,先不去Class的方法列表中查詢,而是先去找cache_t cache
。當系統呼叫過一個方法後,會將其實現IMP
和key
存放到cache中,因為理論上一個方法呼叫過後,被再次呼叫的概率很大。關於方法呼叫,我們將會在別的章節描述。
class_data_bits_t bits
:這是Class
的核心,其本質是一個可以被Mask的指標型別。根據不同的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
, 可以理解為一個‘複合指標’。什麼意思呢,就是bits不僅包含了指標,同時包含了Class
的各種異或flag,來說明Class
的屬性。把這些資訊複合在一起,僅用一個uint指標bits
來表示。當需要取出這些資訊時,需要用對應的以FAST_
字首開頭的flag掩碼對bits
做按位與操作。
例如,我們需要取出Classs的核心資訊class_rw_t
, 則需要呼叫方法:
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
該方法返回一個class_rw_t*
,需要對bits
進行FAST_DATA_MASK
的與操作。
bits在記憶體中有三種位排列方式:
32位
0 | 1 | 2-31 |
---|---|---|
FAST_IS_SWIFT | FAST_HAS_DEFAULT_RR | FAST_DATA_MASK |
64位相容版
0 | 1 | 2 | 3-46 | 47-63 |
---|---|---|---|---|
FAST_IS_SWIFT | FAST_HAS_DEFAULT_RR | FAST_REQUIRES_RAW_ISA | FAST_DATA_MASK | 空閒 |
64位不相容版
0 | 1 | 2 | 3-46 | 47 | 48 | 49 | 50 | 51 | 52-63 |
---|---|---|---|---|---|---|---|---|---|
FAST_IS_SWIFT | FAST_REQUIRES_RAW_ISA | FAST_HAS_CXX_DTOR | FAST_DATA_MASK | FAST_HAS_CXX_CTOR | FAST_HAS_DEFAULT_AWZ | FAST_HAS_DEFAULT_RR | FAST_ALLOC | FAST_SHIFTED_SIZE_SHIFT | 空閒 |
不相容版本的巨集定義如下:
// class is a Swift class
#define FAST_IS_SWIFT (1UL<<0)
// class's instances requires raw isa
#define FAST_REQUIRES_RAW_ISA (1UL<<1)
// class or superclass has .cxx_destruct implementation
// This bit is aligned with isa_t->hasCxxDtor to save an instruction.
#define FAST_HAS_CXX_DTOR (1UL<<2)
// data pointer
#define FAST_DATA_MASK 0x00007ffffffffff8UL
// class or superclass has .cxx_construct implementation
#define FAST_HAS_CXX_CTOR (1UL<<47)
// class or superclass has default alloc/allocWithZone: implementation
// Note this is is stored in the metaclass.
#define FAST_HAS_DEFAULT_AWZ (1UL<<48)
// class or superclass has default retain/release/autorelease/retainCount/
// _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference
#define FAST_HAS_DEFAULT_RR (1UL<<49)
// summary bit for fast alloc path: !hasCxxCtor and
// !instancesRequireRawIsa and instanceSize fits into shiftedSize
#define FAST_ALLOC (1UL<<50)
// instance size in units of 16 bytes
// or 0 if the instance size is too big in this field
// This field must be LAST
#define FAST_SHIFTED_SIZE_SHIFT 51
讓我們再看一下Class
的核心結構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;
// 和繼承相關的東西
Class firstSubclass;
Class nextSiblingClass;
// Class對應的 符號名稱
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
中,有三個陣列method_array_t
, property_array_t
, protocol_array_t
:
struct class_rw_t {
...
const class_ro_t *ro; // 類不可修改的原始核心
// 下面三個array,method,property, protocol,可以被runtime 擴充套件,如Category
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
...
}
這三個陣列是可以被runtime動態擴充套件的。
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 *
型別,也就是類的基本資訊。因為在呼叫realizeClass
方法前,Category定義的各種方法,屬性還沒有附加到class上,因此只能夠返回類的基本資訊。
而當我們呼叫realizeClass
時,會在函式內部將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);
}
得出結論,在class
沒有呼叫realizeClass
之前,不是真正完整的類。
objc_object & objc_class
如果我們再回頭看一下objc_object
和objc_class
的定義,可以發現object和class是你中有我,我中有你的:
struct objc_object {
private:
isa_t isa; // unit聯合,可以表示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
,可以表示當前類屬於(注意不是繼承)哪個類。而這種說明類是屬於哪個類的類,我們稱之為元類(meta-class
)。
這裡再重申一遍,元類不是類的父類。至於元類的用途,我們將會在OC的訊息轉發中詳細講解。現在只需要知道,每一個類都有一個與其對應的元類。
id
我們可以用id表示任意型別的類例項變數。在runtime中,id是這樣定義的:
typedef struct objc_object *id;
其實是一個objc_object *
,因為objc_object
的isa
存在,所有runtime是可以知道id型別對應的真正型別的。這個和C裡面的void *
還是有區別的。