IOS底層原理之NSObject的結(jié)構(gòu)

一、疑惑

在OC程序中,我們知道NSObject是“萬(wàn)物之源”,所有的類的都繼承自NSObject,我們疑惑的是在OC的底層NSObject是什么樣的?類的結(jié)構(gòu)在OC底層是什么樣的?我們?cè)陬愔卸x的屬性、成員變量、方法、實(shí)現(xiàn)的協(xié)議等是以什么樣的形式存在的?這篇文章我們將深入OC底層探究NSObject的結(jié)構(gòu)。

二、OC底層的NSObject

1、clang命令獲取main.m的C++代碼

為了知道NSObject底層是什么樣的,clang也許是一個(gè)選擇。

Clang是一個(gè)由Apple主導(dǎo)編寫(xiě),基于LLVM的C/C++/Objective-C編譯器。如果你不知道clang,可以在這里找到你想要的。

在工程目錄中的main.m文件目錄下進(jìn)入到終端,輸入如下命令

clang -rewrite-objc main.m -o main.cpp

該命令會(huì)將main.m編譯成C++的代碼,但是不同平臺(tái)支持的代碼肯定是不一樣的。

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 輸出的CPP文件
如果需要鏈接其他框架,使用-framework參數(shù)。比如-framework UIKit

在終端輸入命令以后,會(huì)生成一個(gè)main.cpp文件。打開(kāi)main.cpp文件,直接將拉到最下面,我們會(huì)看到這樣的一段代碼。

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_q5_25qtkmds1pz_0k3brcv05ft00000gn_T_main_218cf4_mi_0,person);
    }
    return 0;
}

這段代碼便是main函數(shù)的底層實(shí)現(xiàn)。我們有看到我們熟悉的Person,在這里我們關(guān)心的是Person的定義什么樣的。

typedef struct objc_object Person;
typedef struct objc_class *Class;
struct objc_object {
    Class _Nonnull isa __attribute__((deprecated));
};

__OBJC_RW_DLLIMPORT struct objc_class *objc_getClass(const char *);//獲取當(dāng)前類
__OBJC_RW_DLLIMPORT struct objc_class *class_getSuperclass(struct objc_class *);//獲取superClass
__OBJC_RW_DLLIMPORT struct objc_class *objc_getMetaClass(const char *);//獲取元類

我們看到Person在底層其實(shí)是一個(gè)objc_object的結(jié)構(gòu)體,objc_object內(nèi)部有一個(gè)objc_class類型的isa,在前面的詳細(xì)了解isa這一篇文章中我們已經(jīng)分析了isa的走位關(guān)系。

  1. 實(shí)例對(duì)象的isa指向的是類;
  2. 類的isa指向的元類;
  3. 元類指向根元類;
  4. 根元類指向自己;
  5. NSObject的父類是nil,根元類的父類是NSObject。

這樣分析起來(lái)我們有理由相信NSObject的OC底層實(shí)現(xiàn)是objc_object,objc_class是類的OC底層實(shí)現(xiàn),而且兩者之間應(yīng)該還會(huì)存在這謀種關(guān)系。
我們?cè)賮?lái)看下在main.cpp中還能找到些什么熟悉的東西。

//方法
typedef struct objc_method *Method;
//成員變量
typedef struct objc_ivar *Ivar;
//category
typedef struct objc_category *Category;
//屬性列表
typedef struct objc_property *objc_property_t;

2、Objc底層源碼分析

我們從main.cpp找到的東西有限,而且代碼過(guò)于龐大,分析起來(lái)很繁雜,下面我們從Objc源碼開(kāi)始分析。

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

// objc_class繼承于objc_object,因此
// objc_class中也有isa結(jié)構(gòu)體
struct objc_class : objc_object {
    // Class ISA; //8字節(jié)
    Class superclass;//8字節(jié)
    // 緩存的是指針和vtable,目的是加速方法的調(diào)用  cache占16字節(jié)
    cache_t cache;             // formerly cache pointer and vtable
    // class_data_bits_t 相當(dāng)于是class_rw_t 指針加上rr/alloc標(biāo)志
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    ...
};

這段代碼是objc_object和objc_class的代碼,我們看到objc_class是從objc_object中繼承而來(lái),
所以objc_class中也有isa結(jié)構(gòu)體。objc_object和objc_class之間的關(guān)系如下圖所示。


objc_object和objc_class關(guān)系圖
  • isa指向元類的指針,如果你不知道什么是元類,可以看 Classes and Metaclasses這篇文章。
  • superclass 當(dāng)前類的父類。
  • cache 用于緩存指針和vtable( formerly cache pointer and vtable)。

bits是什么?這里我們重點(diǎn)探索下bits。

struct class_data_bits_t {
    // 相當(dāng)于 unsigned long bits; 占64位
    // bits實(shí)際上是一個(gè)地址(是一個(gè)對(duì)象的指針,可以指向class_ro_t,也可以指向class_rw_t)
    uintptr_t bits;
};

在objc_class結(jié)構(gòu)體中關(guān)于class_data_bits_t的注釋:class_rw_t * plus custom rr/alloc flags,意思是class_data_bits_t相當(dāng)于class_rw_t * 加上rr/alloc標(biāo)志。它提供了便捷的方式data()方法返回class_rw_t *指針。

 class_rw_t *data() {
     // 這里的bits就是class_data_bits_t bits;
     return bits.data();
 }

 class_rw_t* data() {
     // FAST_DATA_MASK的值是0x00007ffffffffff8UL
     // bits和FAST_DATA_MASK按位與,實(shí)際上就是取了bits中的[3,47]位
     return (class_rw_t *)(bits & FAST_DATA_MASK);
 }

這里將 bits 與 FAST_DATA_MASK 進(jìn)行位運(yùn)算,只取其中的 [3, 47] 位轉(zhuǎn)換成 class_rw_t * 返回。


objc_class中的data()方法調(diào)用了class_data_bits_t 結(jié)構(gòu)體中的 data() 方法,返回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;
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    Class firstSubclass;
    Class nextSiblingClass;
    ...
};

這段是代碼是class_rw_t結(jié)構(gòu)體的代碼,我們驚奇發(fā)現(xiàn)這里有methods(方法)、properties(屬性)、protocols(協(xié)議)這些信息,那么我們所需要的類中的方法、屬性、成員變量等信息是不是在這里存儲(chǔ)的呢?下面我來(lái)驗(yàn)證下。

我們定義了一個(gè)Person類,在Person類中定義了屬性name,age,成員變量hobby,實(shí)例方法sayHello,類方法eat,斷點(diǎn)進(jìn)入Debug,使用LLVM指令來(lái)查看內(nèi)存信息。

在objc_class的結(jié)構(gòu)中,isa占8字節(jié),superclass占用8字節(jié),cache占用16個(gè)字節(jié),將cls的地址偏移32個(gè)字節(jié)即0x20便是bits的地址。

struct cache_t {
  struct bucket_t *_buckets;//8字節(jié)
  mask_t _mask;//4字節(jié)
  mask_t _occupied;//4字節(jié)
};
typedef uint32_t mask_t;

如上截圖所示,在class_rw_t中如愿找到了我們定義的兩個(gè)屬性name和age,同樣的也找到了sayHello方法以及name和age的getter/setter方法。但是成員變量hobby和類方法eat在class_rw_t并沒(méi)有找到。

我們有注意到在class_rw_t結(jié)構(gòu)中有const class_ro_t *ro這樣一個(gè)東西,這是一個(gè)常量結(jié)構(gòu)體指針,那么我們猜測(cè)成員變量和hobby是不是存放在ro中呢?來(lái)看一下class_ro_t的結(jié)構(gòu)。

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;
    // 協(xié)議列表
    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中有ivars這樣一個(gè)東西,這里存放的就是成員變量,除此之外還有協(xié)議列表baseProtocols,屬性列表baseProperties,方法列表baseMethodList。接下來(lái)我們利用LLVM來(lái)看下ro的內(nèi)存信息吧。

如上圖所示我們?nèi)缭刚业搅薶obby,這就證明了成員變量是存放在class_ro_t中的,但是很遺憾的是類方法eat并沒(méi)有找到。那么類方法會(huì)不會(huì)存在于元類中呢。

上圖是拿到的元類來(lái)讀取內(nèi)存信息,我們可以找到類方法eat,證明了類方法是存在于元類中的。

上面的分析我們得出一下結(jié)論
1. 類的對(duì)象方法、屬性、實(shí)現(xiàn)的協(xié)議,成員變量是存在類里面的,其中成員變量是存放在class_ro_t中,class_ro_t也會(huì)存放在編譯期間確定的屬性、方法以及遵循的協(xié)議。class_rw_t中也會(huì)存放類的屬性、方法以及遵循的協(xié)議。
2. 類方法在元類中,對(duì)象方法在本類中。

通過(guò)上面的分析我們已然知道了類屬性、方法、成員變量、遵守的協(xié)議等信息的存儲(chǔ)位置,但是我們還不知道這些信息是怎么樣存放進(jìn)去的。

3、類的信息存儲(chǔ)過(guò)程

通過(guò)前面的分析我們知道了,objc_class結(jié)構(gòu)中的data()方法可以返回類的信息,那么我們便可以通過(guò)setData(class_rw_t *newData)這個(gè)方法追本溯源找到了setData的調(diào)用這realizeClass方法。

static Class realizeClass(Class cls)
{
    runtimeLock.assertLocked();

    const class_ro_t *ro;
    class_rw_t *rw;
    Class supercls;
    Class metacls;
    bool isMeta;

    if (!cls) return nil;
    // 如果類已經(jīng)實(shí)現(xiàn)了,直接返回
    if (cls->isRealized()) return cls;
    assert(cls == remapClass(cls));
    // fixme verify class is not in an un-dlopened part of the shared cache?
    // 編譯期間,cls->data指向的是class_ro_t結(jié)構(gòu)體
    ro = (const class_ro_t *)cls->data();
    if (ro->flags & RO_FUTURE) {
        // rw結(jié)構(gòu)體已經(jīng)被初始化(正常不會(huì)執(zhí)行到這里)
        // 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 {
        // 正常的類都是執(zhí)行到這里
        // Normal class. Allocate writeable class data.
        // 初始化class_rw_t結(jié)構(gòu)體
        rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
        // 賦值class_rw_t的class_ro_t,也就是ro
        rw->ro = ro;
        rw->flags = RW_REALIZED|RW_REALIZING;
        // cls->data 指向class_rw_t結(jié)構(gòu)體
        cls->setData(rw);  
        ...
    };

我們看到其實(shí)最開(kāi)始的時(shí)候cls->data是指向class_ro_t的,然后才會(huì)把class_ro_t設(shè)置到class_rw_t中。那么在realizeClass之前,在class_rw_t中一定拿不到類的相關(guān)信息的。
上面在realizeClass里面下的斷點(diǎn),只需要判斷cls == 當(dāng)前類的地址就好了,我這里的地址是0x100001490。



如上圖所示我們?cè)趓ealizeClass里面下斷點(diǎn),這個(gè)時(shí)候在class_rw_t中并沒(méi)有類的相關(guān)信息,而在class_ro_t中卻可以找到類的相關(guān)信息。那是因?yàn)樵谶@之前class_data_bits_t *data 指向的是一個(gè) class_ro_t * 指針。


但是我們前面也分析了class_rw_t結(jié)構(gòu),是可以拿到類的相關(guān)信息的,這是因?yàn)閳?zhí)行了methodizeClass方法。methodizeClass方法就是向class_rw_t中添加類的方法列表、協(xié)議列表、屬性列表,包括category的方法。

static void methodizeClass(Class cls)
{
    ...
    // Install methods and properties that the class implements itself.
    // 將class_ro_t中的methodList添加到class_rw_t結(jié)構(gòu)體中的methodList
    method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
        rw->methods.attachLists(&list, 1);
    }

    // 將class_ro_t中的propertyList添加到class_rw_t結(jié)構(gòu)體中的propertyList
    property_list_t *proplist = ro->baseProperties;
    if (proplist) {
        rw->properties.attachLists(&proplist, 1);
    }

    // 將class_ro_t中的protocolList添加到class_rw_t結(jié)構(gòu)體中的protocolList
    protocol_list_t *protolist = ro->baseProtocols;
    if (protolist) {
        rw->protocols.attachLists(&protolist, 1);
    }

    // Root classes get bonus method implementations if they don't have 
    // them already. These apply before category replacements.
    if (cls->isRootMetaclass()) {
        // root metaclass
        addMethod(cls, SEL_initialize, (IMP)&objc_noop_imp, "", NO);
    }

    // Attach categories.
    // 添加category方法
    category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
    attachCategories(cls, cats, false /*don't flush caches*/);
  
    ...
}

在methodizeClass這個(gè)方法里面,將ro中的類的屬性、對(duì)象方法,遵守的協(xié)議,category方法都添加到了class_rw_t中。這樣就如我們前面所分析的那樣,在class_rw_t結(jié)構(gòu)中可以拿到類的相關(guān)信息了。由此就形成以這樣的一個(gè)結(jié)構(gòu)。


三、總結(jié)

  1. 在realizeClass方法之前,class_data_bits_t *data 指向的是一個(gè) class_ro_t * 指針。所以在class_rw_t中找不到類的方法、屬性以及協(xié)議。
  2. 在realizeClass方法會(huì)通過(guò)methodizeClass方法將類的方法、屬性、協(xié)議總class_ro_t中添加到class_rw_t中。
  3. class_rw_t結(jié)構(gòu)體中的ro是一個(gè)class_ro_t類型的常量結(jié)構(gòu)體指針,所以在realizeClass方法之后ro中的內(nèi)容便不可修改,我手動(dòng)添加的方法也只是修改了class_rw_t中的方法列表中。
  4. 類的成員變量存儲(chǔ)在class_ro_t結(jié)構(gòu)體中,而不是class_rw_t結(jié)構(gòu)體中。
  5. 類的類方法在類的元類中,對(duì)象方法才在本類中。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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