1. 程式人生 > >Objective-C runtime機制(1)——基本資料結構:objc_object & objc_class

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系統對應資料結構的支援。如isKindOfClassisMemberOfClass 檢查類是否屬於指定的Class的繼承體系中;responderToSelector
    檢查物件是否能響應指定的訊息;conformsToProtocol 檢查物件是否遵循某個協議;methodForSelector返回指定方法實現的地址。
  • Runtime函式:Runtime 系統是一個由一系列函式和資料結構組成,具有公共介面的動態共享庫。標頭檔案存放於/usr/include/objc目錄下。許多函式允許你用純C程式碼來重複實現 Objc 中同樣的功能。雖然有一些方法構成了NSObject類的基礎,但是你在寫 Objc 程式碼時一般不會直接用到這些函式的,除非是寫一些 Objc 與其他語言的橋接或是底層的debug工作。在Objective-C Runtime Reference
    中有對 Runtime 函式的詳細文件。

就如在我們在前傳篇中提到的,所謂的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_tClass 型別其實是 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的指標,它指向了一個以_keyIMP對應的快取節點。

runtime方法呼叫的流程是,當要呼叫一個方法時,先不去Class的方法列表中查詢,而是先去找cache_t cache 。當系統呼叫過一個方法後,會將其實現IMPkey存放到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_tprotocol_list_tivar_list_tproperty_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_classdata()方法最初返回的是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_objectobjc_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_objectisa存在,所有runtime是可以知道id型別對應的真正型別的。這個和C裡面的void * 還是有區別的。