Objective-C runtime機制(1)——基本數(shù)據(jù)結(jié)構(gòu):objc_object & objc_class

原文地址

什么是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)系,我們可以用下面的圖來更清晰的了解:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容