02-OC類(lèi)的底層分析

OC底層原理探索文檔匯總

整體分析類(lèi)在底層的結(jié)構(gòu)斟赚,以及對(duì)類(lèi)的操作在底層是如何實(shí)現(xiàn)的鹿寻,重點(diǎn)包括cache和bits的分析届氢。

主要內(nèi)容:

  1. 類(lèi)的底層結(jié)構(gòu)objc_class的認(rèn)識(shí),及objc_class與objc_object的關(guān)系
  2. isa的認(rèn)識(shí),isa的走向以及類(lèi)的繼承關(guān)系
  3. cache的詳細(xì)解讀
  4. bits的詳細(xì)解讀

1来屠、類(lèi)的底層結(jié)構(gòu)objc_class的認(rèn)識(shí)

先查找類(lèi)的底層結(jié)構(gòu)是什么承二,可以通過(guò)Clang編譯查看底層得知是通過(guò)objc_class來(lái)創(chuàng)建的榆鼠。同時(shí)分析objc_class和objc_object的關(guān)系。

1.1 創(chuàng)建一個(gè)WYPerson類(lèi)

@interface WYPerson : NSObject

@property (nonatomic, assign) int age;

@end

@implementation WYPerson

- (void)eat{
    NSLog(@"eat");
}

1.2 底層結(jié)構(gòu)體

首先看一下Class結(jié)構(gòu)體是什么

在objc源碼中可以看到Class就是通過(guò)objc_class結(jié)構(gòu)體定義的亥鸠,它就表示一個(gè)類(lèi)

typedef struct objc_class *Class;

NSObject的結(jié)構(gòu)體

代碼:

#ifndef _REWRITER_typedef_NSObject
#define _REWRITER_typedef_NSObject
typedef struct objc_object NSObject;
typedef struct {} _objc_exc_NSObject;
#endif

struct NSObject_IMPL {
    Class isa;
};

說(shuō)明:

  • 通過(guò)objc_object結(jié)構(gòu)體定義了一個(gè)NSObject對(duì)象的結(jié)構(gòu)體
  • 而NSObject_IMPL是通過(guò)偽繼承來(lái)實(shí)現(xiàn)的妆够,而Class是通過(guò)objc_class結(jié)構(gòu)體定義的,所以NSObject_IMPL是基于objc_class來(lái)創(chuàng)建的

WYPerson的結(jié)構(gòu)體

代碼:

#ifndef _REWRITER_typedef_WYPerson
#define _REWRITER_typedef_WYPerson
typedef struct objc_object WYPerson;
typedef struct {} _objc_exc_WYPerson;
#endif

extern "C" unsigned long OBJC_IVAR_$_WYPerson$_age;
struct WYPerson_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _age;
};

說(shuō)明:

  • 通過(guò)objc_objcet定義了一個(gè)WYPerson的對(duì)象結(jié)構(gòu)體
  • WYPerson_IMPL也是通過(guò)偽繼承實(shí)現(xiàn)的,這里繼承自NSObject_IMPL读虏,也就是帶有了NSObject的所有成員

1.3 結(jié)構(gòu)體模板

objc_object的認(rèn)識(shí):

源碼:

//可以看到,objc_object中就是一個(gè)isa
struct objc_object {
private:
    isa_t isa;

public://外部可以獲取isa
    // getIsa() allows this to be a tagged pointer object
    Class getIsa();
}

其實(shí)objc_object中還有很多方法责静,我這里只寫(xiě)了getIsa(),更多的方法可以查看objc源碼

說(shuō)明:

  • objc_object只有一個(gè)成員盖桥,就是isa,其他的都沒(méi)有
  • 那么我們創(chuàng)建一個(gè)對(duì)象灾螃,那么多屬性、方法等等在哪里呢揩徊,其實(shí)都在類(lèi)里腰鬼。
  • 對(duì)象通過(guò)isa獲取到類(lèi)信息嵌赠,之后就可以在類(lèi)信息中獲取相應(yīng)的屬性、成員變量熄赡、方法姜挺、協(xié)議了。
  • 此處需要注意的是通過(guò)getIsa()獲取到的isa并不是完整的彼硫,而只有Class信息炊豪。

objc_class的認(rèn)識(shí):

源碼:

struct objc_class : objc_object {//繼承自objc_object,充分證明萬(wàn)物皆對(duì)象
    // Class ISA;               //來(lái)自繼承的objc_object
    Class superclass;           //父類(lèi)
    cache_t cache;             // 方法緩存區(qū),快速查詢(xún)方法
    class_data_bits_t bits;    // 類(lèi)信息拧篮,方法词渤、協(xié)議、屬性串绩、成員變量列表

    class_rw_t *data() const {//獲取bits的數(shù)據(jù)
        return bits.data();
    }
}

說(shuō)明:

  • objc_class繼承自objc_object缺虐,充分說(shuō)明類(lèi)也是一個(gè)對(duì)象,萬(wàn)物起源于objc_object
  • 類(lèi)的結(jié)構(gòu)包括isa礁凡,superclass高氮、cache、bits顷牌。
  • isa的繼承自objc_object獲取的
  • cache用來(lái)存儲(chǔ)緩存的方法信息剪芍,包含sel和imp,可以快速的進(jìn)行消息查找
  • bits的data()包含了方法、協(xié)議韧掩。屬性紊浩、成員變量。

1.4 總結(jié)

類(lèi)對(duì)象的上下層結(jié)構(gòu)圖.png
  1. objc_class是繼承自objc_object疗锐,因此類(lèi)本身也是一個(gè)類(lèi)對(duì)象坊谁,類(lèi)是元類(lèi)的對(duì)象
  2. objc_object作為對(duì)象的底層結(jié)構(gòu)體有一個(gè)isa,說(shuō)明對(duì)象的底層結(jié)構(gòu)只包括isa滑臊。
  3. 所有的對(duì)象口芍、類(lèi)、元類(lèi)雇卷、協(xié)議都有isa成員鬓椭,充分說(shuō)明在面向?qū)ο蟮氖澜缋锶f(wàn)事萬(wàn)物皆對(duì)象
  4. 所有的對(duì)象底層是以objc_object為模板創(chuàng)建的結(jié)構(gòu)體,所有的類(lèi)的底層是以objc_class為模板創(chuàng)建的結(jié)構(gòu)體
  5. 元類(lèi)也有isa关划,因?yàn)樗灿凶约旱念?lèi)就是根元類(lèi)小染,根元類(lèi)的類(lèi)是他自己,所以根元類(lèi)也有自己的isa贮折。
  6. objc_class中包含isa裤翩、superClass、cache调榄、bits踊赠。

2呵扛、isa的認(rèn)識(shí),isa的走向以及類(lèi)的繼承關(guān)系

isa是從objc_object繼承過(guò)來(lái)的筐带,在OC對(duì)象的底層分析中已有詳細(xì)解讀今穿,這里不再分析。只了解isa的走向和類(lèi)的繼承關(guān)系

通過(guò)這個(gè)關(guān)系圖可以形象的了解對(duì)象伦籍、類(lèi)蓝晒、元類(lèi)、根元類(lèi)之間關(guān)系鸽斟。

關(guān)系圖:

走位圖.png

2.1 元類(lèi)

2.1.1 什么是元類(lèi)

  • 元類(lèi)是類(lèi)對(duì)象的類(lèi)拔创,每個(gè)類(lèi)都有一個(gè)獨(dú)一無(wú)二的元類(lèi)用來(lái)存儲(chǔ)類(lèi)的相關(guān)信息。
  • 元類(lèi)的定義和創(chuàng)建的都是系統(tǒng)完成的富蓄,是根據(jù)我們自定義的類(lèi)來(lái)創(chuàng)建的
  • 元類(lèi)本身沒(méi)有名稱(chēng),由于與類(lèi)關(guān)聯(lián)慢逾,名稱(chēng)和類(lèi)名一樣立倍,并且我們無(wú)法使用,無(wú)法直接看到
  • 元類(lèi)用來(lái)存儲(chǔ)類(lèi)信息侣滩,所有的類(lèi)方法都存儲(chǔ)在元類(lèi)中

2.1.2 為什么需要元類(lèi)(元類(lèi)的作用是什么)

  • 元類(lèi)用來(lái)管理類(lèi)本身的信息口注,比如類(lèi)方法就存放在元類(lèi)中
  • 在面向?qū)ο蟮氖澜缰校f(wàn)物皆對(duì)象君珠,類(lèi)也是一個(gè)對(duì)象寝志,叫類(lèi)對(duì)象,而這個(gè)類(lèi)對(duì)象所屬的類(lèi)就是元類(lèi)策添,通過(guò)元類(lèi)就可以將類(lèi)作為對(duì)象材部。
  • 元類(lèi)所屬的是根元類(lèi)。

2.1.3 類(lèi)方法存儲(chǔ)在哪里唯竹?

  • 在上層區(qū)分實(shí)例方法乐导、類(lèi)方法,但是在底層都是函數(shù)浸颓,并無(wú)法區(qū)分實(shí)例方法和類(lèi)方法物臂,因此可以通過(guò)存放的地方不同來(lái)區(qū)分。
  • 類(lèi)方法存儲(chǔ)在元類(lèi)中产上,實(shí)例方法存放在類(lèi)中
  • 類(lèi)方法也是以對(duì)象方法的姿態(tài)存儲(chǔ)在元類(lèi)中

2.2 isa走位分析

2.2.1 根據(jù)圖示的分析

根據(jù)圖可以看出:

  1. 對(duì)象的isa指向類(lèi)
  2. 類(lèi)的isa指向元類(lèi)
  3. 元類(lèi)的isa指向根元類(lèi)
  4. NSObject類(lèi)也指向根元類(lèi)
  5. 根元類(lèi)指向根元類(lèi)自己

2.2.2 驗(yàn)證

在我的前一篇博客對(duì)象的底層結(jié)構(gòu)分析中得知isa中包含有類(lèi)信息棵磷,所以我們可以通過(guò)查看isa中的類(lèi)信息來(lái)驗(yàn)證走位圖中的結(jié)果。

1晋涣、獲取到類(lèi)信息(對(duì)象的isa)

獲取類(lèi)信息.png

說(shuō)明:

  • x/4gx perosn 可以得到包括isa在內(nèi)的person屬性
  • p/x isa值 & ISA_MASK 通過(guò)mask取出isa中的類(lèi)地址
  • x/4gx 類(lèi)地址 得到類(lèi)的信息

2仪媒、查看元類(lèi)信息(類(lèi)的isa)

查看元類(lèi)信息.png

說(shuō)明:

  • x/4gx 類(lèi)信息地址 得到包括isa在內(nèi)的類(lèi)信息
  • p/x isa值 & ISA_MASK 得到根元類(lèi)地址
  • 可以看到類(lèi)信息的isa本身就是類(lèi)信息的地址,沒(méi)有任何其他信息
  • 因此此時(shí)得到的類(lèi)信息的isa地址其實(shí)是LGPerson的元類(lèi)信息

3姻僧、查看根元類(lèi)(元類(lèi)的isa)

查看根元類(lèi)信息.png

說(shuō)明:

  • 查看類(lèi)的isa规丽,發(fā)現(xiàn)是NSObject蒲牧,其實(shí)此時(shí)是根元類(lèi),

4赌莺、驗(yàn)證根元類(lèi)的isa

查看根元類(lèi)的isa.png

說(shuō)明:

  • NSObject根元類(lèi)的isa指向的還是自己
  • 從中還可以看出來(lái)冰抢,類(lèi)的isa直接就是元類(lèi)信息,沒(méi)有其他信息

2.3 繼承關(guān)系分析

  • 繼承關(guān)系不包括對(duì)象艘狭,只是類(lèi)挎扰、元類(lèi)之間的關(guān)系
  • 所有的類(lèi)都繼承自NSObject(NSProxy除外,關(guān)于它的認(rèn)識(shí)請(qǐng)查看博客巢音。遵倦。。)
  • 元類(lèi)也有自己的繼承鏈官撼,最終繼承自NSObject類(lèi)
  • 重點(diǎn)在于NSObject元類(lèi)繼承自NSObject類(lèi)梧躺。同時(shí)NSObject類(lèi)繼承自nil,也就是不繼承任何類(lèi)傲绣,這說(shuō)明NSObject類(lèi)是萬(wàn)物起源掠哥。
  • 類(lèi)的繼承鏈中只有類(lèi),元類(lèi)的繼承鏈中不全是元類(lèi)秃诵,還有一個(gè)NSObject類(lèi)续搀,在元類(lèi)繼承鏈進(jìn)行循環(huán)查找時(shí)要注意這一條。

2.4 總結(jié)

  1. 對(duì)象的isa指向類(lèi)菠净,類(lèi)指向元類(lèi)禁舷,元類(lèi)指向根元類(lèi),根元類(lèi)指向自己
  2. NSObject類(lèi)指向NSObject根元類(lèi)毅往,根元類(lèi)指向自己
  3. 繼承關(guān)系指的是類(lèi)或者元類(lèi)的關(guān)系牵咙,而不是對(duì)象的關(guān)系
  4. 對(duì)象繼承于類(lèi),元類(lèi)也有繼承關(guān)系
  5. 一定要注意根元類(lèi)繼承于NSObject
  6. 最后的NSObject繼承于nil,NSObject是萬(wàn)物起源

3煞抬、 cache

cache是類(lèi)結(jié)構(gòu)體中的一個(gè)屬性霜大,cache用來(lái)存儲(chǔ)已經(jīng)被調(diào)用過(guò)的方法的sel和imp。這樣可以使方法的調(diào)用提高效率革答,主要研究的內(nèi)容是cache的結(jié)構(gòu)和存儲(chǔ)方式战坤,至于如何在cache中查詢(xún)imp,會(huì)在消息發(fā)送過(guò)程中詳細(xì)解讀残拐。

3.1 cache的整體結(jié)構(gòu)認(rèn)識(shí)

cache是類(lèi)結(jié)構(gòu)中用來(lái)存儲(chǔ)已被調(diào)用的方法的sel和imp鍵值對(duì)的一段緩存區(qū)途茫,這里就簡(jiǎn)單看下cache的結(jié)構(gòu)是怎樣的,都包含了哪些內(nèi)容溪食。

3.1.1 cache_t結(jié)構(gòu)體

源碼:

struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED//macOS囊卜、模擬器 -- 主要是架構(gòu)區(qū)分
    // explicit_atomic 顯示原子性,目的是為了能夠 保證 增刪改查時(shí) 線程的安全性
    //等價(jià)于 struct bucket_t * _buckets;
    //_buckets 中放的是 sel imp
    //_buckets的讀取 有提供相應(yīng)名稱(chēng)的方法 buckets()
    explicit_atomic<struct bucket_t *> _buckets;
    explicit_atomic<mask_t> _mask;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 //64位真機(jī)
    explicit_atomic<uintptr_t> _maskAndBuckets;//寫(xiě)在一起的目的是為了優(yōu)化
    mask_t _mask_unused;
    
    //以下都是掩碼,即面具 -- 類(lèi)似于isa的掩碼栅组,即位域
    // 掩碼省略....
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4 //非64位 真機(jī)
    explicit_atomic<uintptr_t> _maskAndBuckets;
    mask_t _mask_unused;

    //以下都是掩碼雀瓢,即面具 -- 類(lèi)似于isa的掩碼,即位域
    // 掩碼省略....
#else
#error Unknown cache mask storage type.
#endif
    
#if __LP64__
    uint16_t _flags;
#endif
    uint16_t _occupied;

    //方法省略.....
}

在源碼中查看玉掸,有三種類(lèi)型刃麸,真機(jī)64位,真機(jī)32位司浪,模擬器或macos泊业,我們研究的是64位真機(jī),

成員:
1)CACHE_MASK_STORAGE_OUTLINED表示非真機(jī)啊易,有屬性_buckets和_mask
2)CACHE_MASK_STORAGE_HIGH_16表示真機(jī)64位吁伺,有屬性_maskAndBuckets和_mask_unused
3)CACHE_MASK_STORAGE_LOW_4表示真機(jī)32位,有屬性_maskAndBuckets和_mask_unused
4)uint16_t _flags;
5)uint16_t _occupied;

宏定義架構(gòu)的判斷

#if defined(__arm64__) && __LP64__//真機(jī)64位
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16
#elif defined(__arm64__) && !__LP64__//真機(jī)32位
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_LOW_4
#else
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_OUTLINED//非真機(jī)
#endif
  • 這里的__arm64是判斷真機(jī)租谈,__LP64是用來(lái)判斷64位篮奄,所以第一個(gè)宏定義是64位真機(jī),第二個(gè)宏定義是32位真機(jī)割去,第三個(gè)宏定義是模擬器和macOS
  • 架構(gòu)判斷宦搬,macos是i386,模擬器是x86劫拗,真機(jī)是arm64。

泛型的查看

屬性中使用到了explicit_atomic<uintptr_t> 矾克,只是一個(gè)泛型页慷,表示傳入一個(gè)類(lèi)型,而這個(gè)explicit_atomic的作用是將這個(gè)類(lèi)型做原子操作胁附。

真機(jī)64位的結(jié)構(gòu)查看
包含_maskAndBuckets/_flags/_occupied酒繁,其中_maskAndBuckets由_mask和buckets組成,這樣做是為了優(yōu)化性能控妻,而在buckets中就存儲(chǔ)有sel和imp州袒。

  • 因此cache_t結(jié)構(gòu)體在64位真機(jī)下包含有_maskAndBuckets、_flags弓候、_occupied
  • _maskAndBuckets郎哭,由_mask和buckets組成,這樣做是為了優(yōu)化性能菇存,而在buckets中就存儲(chǔ)有sel和imp夸研。mask就等于capacity-1,也就是說(shuō)mask就是當(dāng)前緩存量-1
  • _flags:_flags屬性是一些位置標(biāo)記
  • _occupied:sel和imp鍵值對(duì)的數(shù)量

3.1.2 bucket_t結(jié)構(gòu)體

這里可以看到存儲(chǔ)的就是imp和sel,只是真機(jī)和模擬器macOS存儲(chǔ)的順序不一樣.

struct bucket_t {
private:
    // IMP-first is better for arm64e ptrauth and no worse for arm64.
    // SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
    explicit_atomic<uintptr_t> _imp;
    explicit_atomic<SEL> _sel;
#else
    explicit_atomic<SEL> _sel;
    explicit_atomic<uintptr_t> _imp;
#endif
}

3.1.3 總結(jié)

結(jié)構(gòu)圖.png

3.2 脫離源碼環(huán)境查看cache的數(shù)據(jù)變化

通過(guò)在上層對(duì)cache結(jié)構(gòu)進(jìn)行使用依鸥,查看里面的成員是如何進(jìn)行操作的
本質(zhì)就是自己搭建一個(gè)源碼環(huán)境亥至,源碼底層本來(lái)就是結(jié)構(gòu)體,我們?cè)谏蠈右彩怯媒Y(jié)構(gòu)體來(lái)存儲(chǔ)和取用就可以實(shí)現(xiàn)。

代碼:

typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient with 16-bits

struct lg_bucket_t {
    SEL _sel;
    IMP _imp;
};

struct lg_cache_t {
    struct lg_bucket_t * _buckets;
    mask_t _mask;
    uint16_t _flags;
    uint16_t _occupied;
};

struct lg_class_data_bits_t {
    uintptr_t bits;
};

struct lg_objc_class {
    Class ISA;
    Class superclass;
    struct lg_cache_t cache;             // formerly cache pointer and vtable
    struct lg_class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
};


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *p  = [LGPerson alloc];
        Class pClass = [LGPerson class];  // objc_clas
        [p say1];
        [p say2];
        //[p say3];
        //[p say4];

        struct lg_objc_class *lg_pClass = (__bridge struct lg_objc_class *)(pClass);
        NSLog(@"%hu - %u",lg_pClass->cache._occupied,lg_pClass->cache._mask);
        for (mask_t i = 0; i<lg_pClass->cache._mask; i++) {
            // 打印獲取的 bucket
            struct lg_bucket_t bucket = lg_pClass->cache._buckets[i];
            NSLog(@"%@ - %p",NSStringFromSelector(bucket._sel),bucket._imp);
        }

        
        NSLog(@"Hello, World!");
    }
    return 0;
}

打印結(jié)果:

打印結(jié)果.png

疑問(wèn):

  1. _mask是什么姐扮?
  2. _occupied 是什么絮供?
  3. 為什么隨著方法調(diào)用的增多,其打印的occupied 和 mask會(huì)變化茶敏?
  4. 為什么隨著方法調(diào)用的增多壤靶,其打印的occupied 和 mask會(huì)變化?
  5. bucket數(shù)據(jù)為什么會(huì)有丟失的情況睡榆?萍肆,例如2-7中,只有say3胀屿、say4方法有函數(shù)指針
  6. 2-7中say3塘揣、say4的打印順序?yàn)槭裁词莝ay4先打印,say3后打印宿崭,且還是挨著的亲铡,即順序有問(wèn)題?
  7. 打印的cache_t中的_ocupied為什么是從2開(kāi)始葡兑?

3.3 cache的原理分析

在執(zhí)行方法后查看cache_t中的屬性值奖蔓,發(fā)現(xiàn)了一些疑惑點(diǎn),這些需要理解底層原理后才可以解釋?zhuān)ㄟ^(guò)數(shù)值的變化occupied開(kāi)始入手讹堤,查找變化的過(guò)程吆鹤。

3.3.1 查找存儲(chǔ)的函數(shù)

3.3.1.1 在cache_t中查找到設(shè)置occupied屬性的函數(shù)incrementOccupied()
查找incrementOccupied().png
  • 我們?cè)趯?shí)際使用時(shí)發(fā)現(xiàn)當(dāng)調(diào)用一次方法時(shí),occupied會(huì)自增洲守,那么就需要在源碼中查看什么情況下會(huì)出現(xiàn)自增疑务。
  • 通過(guò)occupied++,以及occupied+1都沒(méi)有搜索到,所以就思考是否對(duì)外提供方法來(lái)進(jìn)行操作的。查找到incrementOccupied()函數(shù)
3.3.1.2 查找到該函數(shù)的實(shí)現(xiàn)為自增
incrementOccupied().png
  • 這里可以說(shuō)明occupied的增加是通過(guò)調(diào)用incrementOccupied()函數(shù)實(shí)現(xiàn)的梗醇,所以接下來(lái)就是看誰(shuí)在調(diào)用該函數(shù)
3.3.1.3 全局搜索incrementOccupied()發(fā)現(xiàn)在cache_t的insert函數(shù)有調(diào)用
insert().png
3.3.1.4 全局搜索insert()方法知允,發(fā)現(xiàn)只有cache_fill方法中的調(diào)用符合
cache_fill.png
3.3.1.5 全局搜索cache_fill,發(fā)現(xiàn)在寫(xiě)入之前叙谨,還有一步操作温鸽,即cache讀取,即查找sel-imp手负,如下所示
cache_getImp.png
  • 在這里并沒(méi)有找到如何去調(diào)用的涤垫,可能是系統(tǒng)沒(méi)有給我們展示
  • 但是系統(tǒng)說(shuō)明了它內(nèi)部執(zhí)行的流程,是先讀緩存虫溜,之后再寫(xiě)緩存
3.3.1.6 總結(jié):
  1. 在發(fā)送消息后雹姊,緩存需要先被讀取
  2. 如果緩存中不存在,則需要將得到的sel和imp寫(xiě)入緩存
  3. 寫(xiě)入緩存通過(guò)調(diào)用cache_fill()->insert()->來(lái)實(shí)現(xiàn)

3.3.2 insert()函數(shù)分析

insert()函數(shù)用來(lái)插入sel和imp,重點(diǎn)在于哈希算法

源碼:

ALWAYS_INLINE
void cache_t::insert(Class cls, SEL sel, IMP imp, id receiver)
{
#if CONFIG_USE_CACHE_LOCK
    cacheUpdateLock.assertLocked();
#else
    runtimeLock.assertLocked();
#endif

    ASSERT(sel != 0 && cls->isInitialized());

    // Use the cache as-is if it is less than 3/4 full只有當(dāng)緩存的使用小于等于3/4的時(shí)候直接使用
    mask_t newOccupied = occupied() + 1;//這是一個(gè)臨時(shí)變量衡楞,也就是用來(lái)看做如果添加成功后的換粗占用量
    unsigned oldCapacity = capacity(), capacity = oldCapacity;//獲取到當(dāng)前緩存容量
    if (slowpath(isConstantEmptyCache())) {
        // Cache is read-only. Replace it.
        if (!capacity) capacity = INIT_CACHE_SIZE;//初始容量設(shè)置為4
        reallocate(oldCapacity, capacity, /* freeOld */false);//開(kāi)啟一個(gè)容量為4的空間吱雏,并將舊空間刪掉
    }
    else if (fastpath(newOccupied + CACHE_END_MARKER <= capacity / 4 * 3)) {//occupied() + 1 + 1 <=3/4敦姻,也就是總數(shù)+2小于等于總?cè)萘康?/4就可以直接存入
        // Cache is less than 3/4 full. Use it as-is.
        //當(dāng)緩存的使用量小于等于3/4的時(shí)候直接使用緩存
    }
    else {
        capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;//擴(kuò)容兩倍
        if (capacity > MAX_CACHE_SIZE) {//最大緩存值判斷,不能超過(guò)2^16歧杏,不能無(wú)限擴(kuò)大
            capacity = MAX_CACHE_SIZE;
        }
        reallocate(oldCapacity, capacity, true);//開(kāi)辟空間镰惦,也需要?jiǎng)h除舊空間
    }

    bucket_t *b = buckets();//取出第一個(gè)bucket,為了下面的循環(huán)遍歷
    mask_t m = capacity - 1;//這里可以看到mask就是capacity-1
    mask_t begin = cache_hash(sel, m);//哈希算法犬绒,(mask_t)(uintptr_t)sel & mask
    mask_t i = begin;

    // Scan for the first unused slot and insert there.
    // There is guaranteed to be an empty slot because the
    // minimum size is 4 and we resized at 3/4 full.
    //先查詢(xún)?cè)撐恢檬欠駷榭胀耄绻粸榭涨掖鎯?chǔ)的內(nèi)容是其他sel,說(shuō)明發(fā)生了沖突凯力,就通過(guò)哈希沖突算法進(jìn)行計(jì)算下標(biāo)茵瘾,并再次判斷該坐標(biāo)是否為空
    do {
        //如果該下標(biāo)所在的位置是空的,就直接存儲(chǔ)
        if (fastpath(b[i].sel() == 0)) {
            incrementOccupied();//occupied++咐鹤,每次插入都要給occupied++拗秘,因此他記錄的是緩存的方法的個(gè)數(shù)
            b[i].set<Atomic, Encoded>(sel, imp, cls);//存儲(chǔ)
            return;
        }
        //其他線程已經(jīng)添加過(guò)了,就直接退出
        if (b[i].sel() == sel) {
            // The entry was added to the cache by some other thread
            // before we grabbed the cacheUpdateLock.
            return;
        }
    } while (fastpath((i = cache_next(i, m)) != begin));//哈希沖突算法:i ? i-1 : mask;

    cache_t::bad_cache(receiver, (SEL)sel, cls);
}

【第一步】:開(kāi)辟空間

  1. 得到緩存量的占用量祈惶。 這是一個(gè)臨時(shí)變量雕旨,假如添加成功之后的一個(gè)占用量,用作后面是否進(jìn)行擴(kuò)容的判斷捧请。
  2. 得到當(dāng)前總的緩存量凡涩。 這是當(dāng)前已有的緩存量,主要用來(lái)判斷這一次是否還需要進(jìn)行擴(kuò)容疹蛉。
  3. 首次開(kāi)辟空間活箕。
    • 判斷容量為空,則進(jìn)行初始化可款,首次開(kāi)辟空間
    • 先獲取到開(kāi)辟空間的容量
    • 首次的空間是4
    • 之后調(diào)用reallocate()函數(shù)進(jìn)行開(kāi)辟
    • 傳入的是false讹蘑,不會(huì)刪除舊空間,因?yàn)槭鞘状伍_(kāi)辟
  4. 直接存儲(chǔ)筑舅,不需要擴(kuò)容
    • 當(dāng)緩存的使用小于等于3/4的時(shí)候直接使用緩存
    • 需要注意的是,在當(dāng)前緩存的基礎(chǔ)上再前面先+1陨舱,在這個(gè)判斷里又進(jìn)行了+1翠拣,也就是當(dāng)前緩存+2后小于等于當(dāng)前容量的3/4才不需要擴(kuò)容
    • 這是因?yàn)橐枰紤]多線程的影響
  5. 擴(kuò)容
    • 一次擴(kuò)容是在之前容量的基礎(chǔ)上增加一倍的容量
    • 容量有最大值,是2的16次方
    • 傳入的值是True,所以需要?jiǎng)h除舊空間游盲,開(kāi)辟新空間误墓,擴(kuò)容后需要再次開(kāi)辟空間

【第二步】:計(jì)算存儲(chǔ)位置

通過(guò)哈希算法來(lái)計(jì)算存儲(chǔ)位置,哈希算法肯定會(huì)涉及哈希沖突益缎,哈希沖突后需要再次進(jìn)行哈希沖突計(jì)算來(lái)得到存儲(chǔ)位置谜慌。具體的哈希沖突算法后面再分析。

代碼:

bucket_t *b = buckets();//取出第一個(gè)bucket莺奔,為了下面的循環(huán)遍歷
    mask_t m = capacity - 1;//這里可以看到mask就是capacity-1
    mask_t begin = cache_hash(sel, m);//哈希算法欣范,(mask_t)(uintptr_t)sel & mask
    mask_t i = begin;

    // Scan for the first unused slot and insert there.
    // There is guaranteed to be an empty slot because the
    // minimum size is 4 and we resized at 3/4 full.
    //先查詢(xún)?cè)撐恢檬欠駷榭眨绻粸榭涨掖鎯?chǔ)的內(nèi)容是其他sel,說(shuō)明發(fā)生了沖突恼琼,就通過(guò)哈希沖突算法進(jìn)行計(jì)算下標(biāo)妨蛹,并再次判斷該坐標(biāo)是否為空
    do {
        //如果該下標(biāo)所在的位置是空的症副,就直接存儲(chǔ)
        if (fastpath(b[i].sel() == 0)) {
            incrementOccupied();//occupied++考阱,每次插入都要給occupied++,因此他記錄的是緩存的方法的個(gè)數(shù)
            b[i].set<Atomic, Encoded>(sel, imp, cls);//存儲(chǔ)
            return;
        }
        //其他線程已經(jīng)添加過(guò)了宰翅,就直接退出
        if (b[i].sel() == sel) {
            // The entry was added to the cache by some other thread
            // before we grabbed the cacheUpdateLock.
            return;
        }
    } while (fastpath((i = cache_next(i, m)) != begin));//哈希沖突算法:i ? i-1 : mask;

代碼說(shuō)明:

  • 通過(guò)cache_hash()算法來(lái)得到存儲(chǔ)下標(biāo)
  • 如果該存儲(chǔ)位置的sel()為空噩死,則直接存儲(chǔ)
  • 如果該存儲(chǔ)位置的sel()與需要插入的sel一致颤难,則不存儲(chǔ),直接使用
  • 否則說(shuō)明已經(jīng)沖突了已维,就使用cache_next()哈希沖突算法進(jìn)行計(jì)算存儲(chǔ)位置行嗤,重新進(jìn)行比較

【第三步】:存儲(chǔ)
存儲(chǔ)就是直接設(shè)置即可,存入的有sel衣摩、imp昂验、cls。

代碼:

cache_t::bad_cache(receiver, (SEL)sel, cls);

3.3.3 reallocate()函數(shù)分析

該方法用來(lái)開(kāi)辟新空間艾扮,重點(diǎn)在于如果是擴(kuò)容既琴,需要?jiǎng)h除舊空間。

源碼:

reallocate().png

說(shuō)明:

  • 通過(guò)新的容量來(lái)創(chuàng)建新的bucket_t的空間
  • 通過(guò)setBucketsAndMask()函數(shù)將新的buckets和capacity設(shè)置到cache中
  • 通過(guò)cache_collect_free()函數(shù)刪除舊的buckets

3.3.4 setBucketsAndMask()函數(shù)分析

該方法用來(lái)設(shè)置buckets和mask泡嘴,有三種甫恩,真機(jī)64位、真機(jī)非64位酌予,非真機(jī)磺箕。


setBucketsAndMask().png

3.3.5 cache_collect_free()函數(shù)分析

該方法對(duì)數(shù)據(jù)進(jìn)行垃圾回收

cache_collect_free().png
  • 垃圾回收,清理舊的bucket
  • 擴(kuò)容后不需要移植緩存信息抛虫,這樣可以提高性能松靡,因?yàn)楫?dāng)調(diào)用的方法越多,記錄的會(huì)越多建椰,這樣每次擴(kuò)容移植的信息就越多雕欺,性能會(huì)差,同時(shí)還可以避免一定程度的哈希沖突棉姐。

3.3.6 _garbage_make_room函數(shù)

_garbage_make_room.png
  • 創(chuàng)建垃圾回收空間
  • 如果是第一次屠列,需要分配回收空間
  • 如果不是第一次,則將內(nèi)存段翻倍伞矩,即原有內(nèi)存*2

3.3.7 疑問(wèn)解答

經(jīng)過(guò)對(duì)原理的分析笛洛,接下來(lái)就可以對(duì)前面的疑問(wèn)進(jìn)行解答:

問(wèn):_mask是什么?
答:_mask是指掩碼數(shù)據(jù)乃坤,用于在哈希算法或者哈希沖突算法中計(jì)算哈希下標(biāo)苛让,其中mask 等于capacity - 1沟蔑。

問(wèn):_occupied 是什么?
答:已經(jīng)存儲(chǔ)了sel-imp的的個(gè)數(shù)蝌诡,只要有新的方法調(diào)用溉贿,需要在cache中存儲(chǔ)時(shí),就會(huì)給_occupied+1浦旱。

問(wèn):為什么隨著方法調(diào)用的增多宇色,其打印的occupied 和 mask會(huì)變化?
答:隨著新方法調(diào)用颁湖,自然會(huì)將方法存儲(chǔ)到cache中宣蠕,那么_occupied就會(huì)自增。
而存儲(chǔ)到cache中的sel-imp不斷增加甥捺,需要對(duì)cache擴(kuò)容抢蚀,而mask就是容量-1,所以mask也會(huì)增加镰禾。

問(wèn):bucket數(shù)據(jù)為什么會(huì)有丟失的情況皿曲?,例如2-7中吴侦,只有say3屋休、say4方法有函數(shù)指針
答:在擴(kuò)容時(shí),會(huì)將之前創(chuàng)建的內(nèi)存空間刪除备韧,重新創(chuàng)建新空間劫樟,而不是在之前的基礎(chǔ)上增加空間,因此擴(kuò)容后之前已經(jīng)存儲(chǔ)到cache的sel-imp并不會(huì)存在了织堂。

問(wèn):2-7中say3叠艳、say4的打印順序?yàn)槭裁词莝ay4先打印,say3后打印易阳,且還是挨著的附较,即順序有問(wèn)題?
答:存儲(chǔ)方式是哈希表潦俺,通過(guò)哈希算法計(jì)算下標(biāo)翅睛,并不是順序存儲(chǔ)結(jié)構(gòu),

問(wèn):打印的cache_t中的_ocupied為什么是從2開(kāi)始黑竞?
答:這里是因?yàn)長(zhǎng)GPerson通過(guò)alloc創(chuàng)建的對(duì)象,并對(duì)其兩個(gè)屬性賦值的原因疏旨,屬性賦值很魂,會(huì)隱式調(diào)用set方法,set方法的調(diào)用也會(huì)導(dǎo)致occupied變化

3.3.8 總結(jié)

  • cache存儲(chǔ)了sel-imp,存儲(chǔ)在哈希表中檐涝,通過(guò)哈希算法來(lái)計(jì)算下標(biāo)
  • 首次創(chuàng)建的容量大小是4
  • 當(dāng)存儲(chǔ)的occupied+2大于容量的3/4時(shí)就會(huì)擴(kuò)容
  • 擴(kuò)容并不是在原來(lái)內(nèi)存的基礎(chǔ)上擴(kuò)展內(nèi)存遏匆,而是刪除舊空間法挨,創(chuàng)建新空間
  • 一次擴(kuò)容是原來(lái)容量的2倍,容量最多是2的16次方

3.4 掩碼計(jì)算

主要介紹maskAndBuckets的存儲(chǔ)格式幅聘,以及如何通過(guò)掩碼進(jìn)行計(jì)算分別獲取maskAndBuckets凡纳、mask、buckets帝蒿。

3.4.1 存儲(chǔ)格式:

存儲(chǔ)格式.png
  • maskAndBuckets總共64位
  • mask占有高16位
  • buckets占有低44位
  • 中間4位對(duì)于消息發(fā)送會(huì)更有優(yōu)勢(shì)荐糜,在查詢(xún)緩存時(shí)會(huì)用到(這里先記一下,后面在消息發(fā)送的分析中會(huì)用到)

3.4.2 掩碼數(shù)據(jù)

掩碼.png
  • maskShift用來(lái)計(jì)算mask的葛超,將maskAndBuckets向右平移maskShift個(gè)位數(shù)就得到了mask
  • maskZeroBits是存儲(chǔ)中間4位的暴氏,拿它可以用來(lái)計(jì)算buckets的位數(shù),以此可以得到buckets的面具
  • maxMask表示最大的mask
    • mask = 容量-1
    • 最大容量就是2^16绣张,所以mask = 2^16-1
    • mask就占有16位
  • bucketsMask就是buckets的掩碼答渔,是一個(gè)后44位全為1的數(shù)。
    • 計(jì)算方式為1<<(maskShift-maskZeroBits) -1
    • maskShift-maskZeroBits = 48-4 = 44
    • 1<<44得到的數(shù)據(jù)是在第45位為1侥涵,后44為0
    • 1<<44-1得到的數(shù)據(jù)就是后44位都為1了

3.4.3 掩碼計(jì)算:

buckAndBuckets的計(jì)算:

buckAndBuckets.png
  • 傳入的值newBuckets就是新開(kāi)辟的buckets
  • 傳入的newMask就是mask
  • __mindmap__topic先將mask向左平移48位沼撕,放置到高16位
  • 之后再和buckets或一下,就將buckets放置到后44位了
    • 這里應(yīng)該用|芜飘,也就是只要有1务豺,就顯示為1
    • 這樣就可以把前16位保存下來(lái),后44位也保存下來(lái)

buckets:

buckets.png
  • bucketsMask 1<<44 -1
    • 1<<44 燃箭。 在第45位為1冲呢,后44為0
    • 1<<44 -1。 減去1招狸,就是后44位都為1了
  • 所以跟bucketsMask相與就得到后44位的buckets

mask:

mask計(jì)算.png

  • 向右平移48位敬拓,也就是向前16位移到最后,得到mask

3.5 哈希算法

這里要著重注意裙戏,因?yàn)樵诳焖傧⒉檎視r(shí)乘凸,通過(guò)傳入的sel在cache中查詢(xún)imp,就需要通過(guò)哈希算法累榜∮冢快速查找是通過(guò)匯編來(lái)實(shí)現(xiàn)的,因此如果這里不清楚壹罚,對(duì)于后面的查找過(guò)程就更難以理解了葛作。

3.5.1 哈希簡(jiǎn)單認(rèn)識(shí)

如何進(jìn)行地址存儲(chǔ)
bucket存儲(chǔ)在哈希表中,所以需要通過(guò)哈希算法來(lái)存儲(chǔ)猖凛,將存儲(chǔ)的對(duì)象作為變量赂蠢,通過(guò)執(zhí)行一個(gè)哈希算法的哈希函數(shù)進(jìn)行計(jì)算得到存儲(chǔ)地址。

哈希沖突
如果兩個(gè)存儲(chǔ)對(duì)象經(jīng)過(guò)哈希算法計(jì)算的到的哈希地址是同一個(gè)地址辨泳,則就表示出現(xiàn)了哈希沖突虱岂,而要解決就需要將該變量用一個(gè)哈希沖突算法計(jì)算得到新的哈希地址玖院。如果仍然沖突,則繼續(xù)用哈希沖突函數(shù)計(jì)算得到新的哈希地址

3.5.2 存儲(chǔ)邏輯:

源碼:

bucket_t *b = buckets();//取出第一個(gè)bucket第岖,為了下面的循環(huán)遍歷
    mask_t m = capacity - 1;//這里可以看到mask就是capacity-1
    mask_t begin = cache_hash(sel, m);//哈希算法难菌,(mask_t)(uintptr_t)sel & mask
    mask_t i = begin;

    // Scan for the first unused slot and insert there.
    // There is guaranteed to be an empty slot because the
    // minimum size is 4 and we resized at 3/4 full.
    //先查詢(xún)?cè)撐恢檬欠駷榭眨绻粸榭涨掖鎯?chǔ)的內(nèi)容是其他sel蔑滓,說(shuō)明發(fā)生了沖突郊酒,就通過(guò)哈希沖突算法進(jìn)行計(jì)算下標(biāo),并再次判斷該坐標(biāo)是否為空
    do {
        //如果該下標(biāo)所在的位置是空的烫饼,就直接存儲(chǔ)
        if (fastpath(b[i].sel() == 0)) {
            incrementOccupied();//occupied++猎塞,每次插入都要給occupied++,因此他記錄的是緩存的方法的個(gè)數(shù)
            b[i].set<Atomic, Encoded>(sel, imp, cls);//存儲(chǔ)
            return;
        }
        //其他線程已經(jīng)添加過(guò)了杠纵,就直接退出
        if (b[i].sel() == sel) {
            // The entry was added to the cache by some other thread
            // before we grabbed the cacheUpdateLock.
            return;
        }
    } while (fastpath((i = cache_next(i, m)) != begin));//哈希沖突算法:i ? i-1 : mask;

1荠耽、先通過(guò)哈希算法得到哈希地址
2、判斷如果改地址沒(méi)有存儲(chǔ)sel比藻,則直接存入
3铝量、如果已經(jīng)存入,但存入的就是想要存入的sel银亲,則直接返回(因?yàn)槎嗑€程)
4慢叨、如果已經(jīng)存入且是其他的sel,則說(shuō)明沖突
5务蝠、沖突后進(jìn)行哈希沖突算法計(jì)算的到哈希地址拍谐,
6、如果哈希地址與最早的哈希地址不一致馏段,則進(jìn)入執(zhí)行
7轩拨、如果相等,則直接退出院喜,因?yàn)楣_突地址使用了上一輪的i來(lái)進(jìn)行計(jì)算
* 如果相等了亡蓉,下次計(jì)算仍然相等,所以相等之后就停止不再計(jì)算喷舀,否則會(huì)產(chǎn)生死循環(huán)
* 這種情況下也就是哈希沖突算法把所有的位置都遍歷了一遍砍濒,所以就直接退出。

3.5.3 哈希算法:

源碼:

static inline mask_t cache_hash(SEL sel, mask_t mask) 
{
    return (mask_t)(uintptr_t)sel & mask;
}
  • 這里也可以看到sel與mask相與得到哈希地址
  • 而這個(gè)mask就是容量-1

3.5.4 哈希沖突算法:

源碼:

#if __arm__  ||  __x86_64__  ||  __i386__
// objc_msgSend has few registers available.
// Cache scan increments and wraps at special end-marking bucket.
#define CACHE_END_MARKER 1
static inline mask_t cache_next(mask_t i, mask_t mask) {
    return (i+1) & mask;//將下標(biāo)+1硫麻,再與上mask
}

//arm64爸邢,我們看這個(gè)
#elif __arm64__
// objc_msgSend has lots of registers available.
// Cache scan decrements. No end marker needed.
#define CACHE_END_MARKER 0
static inline mask_t cache_next(mask_t i, mask_t mask) {
    return i ? i-1 : mask;//判斷如果i存在,將下標(biāo)-1拿愧,也就是向前一位存儲(chǔ)杠河,如果為0,也就是計(jì)算到第一個(gè)位置,就直接放到mask感猛,也就是最后一個(gè)位置
}
  • 這里可以看到其實(shí)哈希沖突算法很簡(jiǎn)單,只是向前移動(dòng)一位
  • 一直移動(dòng)到第一位仍然沖突奢赂,就放到最后一位陪白。之后繼續(xù)向前移動(dòng)
  • 一直移動(dòng)到最初哈希算法得到的值時(shí)就會(huì)退出循環(huán)

總結(jié):
1、先通過(guò)哈希算法得到哈希地址
2膳灶、判斷如果已經(jīng)存在一個(gè)值但是不是當(dāng)前需要存入的值咱士,就說(shuō)明哈希沖突了,就執(zhí)行哈希沖突算法
3轧钓、如果哈希地址中沒(méi)有sel序厉,就直接存入
3、如果哈希地址有sel毕箍,并且就是這次需要存入的值弛房,說(shuō)明其他線程已經(jīng)存入了,就直接返回即可

幾個(gè)小疑問(wèn)的解答

問(wèn):
為什么要?jiǎng)h掉重新創(chuàng)建而柑,而不是在原來(lái)的基礎(chǔ)上增加內(nèi)存文捶?
答:
擴(kuò)容后哈希算法的mask會(huì)變,導(dǎo)致計(jì)算出來(lái)的哈希地址也會(huì)變更容易沖突媒咳,而且在原來(lái)基礎(chǔ)上增加內(nèi)存不方便粹排。

問(wèn):
既然已經(jīng)丟棄掉,為什么還要擴(kuò)容了涩澡?
答:
因?yàn)檫@表明他的方法調(diào)用數(shù)量可以達(dá)到擴(kuò)容后的容量顽耳,所以就擴(kuò)容,免得下回還需要丟棄掉妙同。

問(wèn):
為什么調(diào)用方法了但是buckets中沒(méi)有拿到
答:
因?yàn)椴灰欢ù娴谝粋€(gè)射富,哈希算法得到的地址是隨機(jī)的,如果只調(diào)了一個(gè)方法渐溶,則會(huì)存在0辉浦,1,2三個(gè)地方都有可能茎辐。

3.6 驗(yàn)證

下面通過(guò)調(diào)用方法實(shí)際驗(yàn)證一下看看是不是真的會(huì)存儲(chǔ)進(jìn)去宪郊。
通過(guò)LLDB查看當(dāng)調(diào)用一個(gè)方法后,會(huì)在cache中的bucket存儲(chǔ)該方法的sel和Imp拖陆,以此就可以做到方法的緩存弛槐。

方法創(chuàng)建:

方法創(chuàng)建.png

方法調(diào)用:

方法調(diào)用.png

分析過(guò)程:

LLDB分析.png
  • 跟之前一樣,就是查找這個(gè)對(duì)象的內(nèi)存
  • 這里需要偏移16個(gè)字節(jié)
  • 得到cache之后就可以通過(guò)上面學(xué)到的cache_t的結(jié)構(gòu)來(lái)獲取到sel和imp了
  • 從源碼的分析中依啰,我們知道sel-imp是在cache_t的_buckets屬性中(目前處于macOS環(huán)境)乎串,而在cache_t結(jié)構(gòu)體中提供了獲取_buckets屬性的方法buckets()
  • 獲取了_buckets屬性,就可以獲取sel-imp了速警,這兩個(gè)的獲取在bucket_t結(jié)構(gòu)體中同樣提供了相應(yīng)的獲取方法sel() 以及 imp(pClass)

這里可以看到方法調(diào)用后在cache->bucket->sel->imp是可以查找到該方法的實(shí)現(xiàn)的

3.7 總結(jié)

  1. cache存儲(chǔ)了sel和imp,可以進(jìn)行快速消息發(fā)送叹誉,sel-imp存儲(chǔ)在哈希表中鸯两,通過(guò)哈希算法來(lái)計(jì)算下標(biāo)。
  2. cache中的maskAndBuckets中包含有buckets长豁,而buckets包含有多個(gè)bucket钧唐,每個(gè)bucket存儲(chǔ)了一個(gè)sel和imp的鍵值對(duì)。
  3. maskAndBuckets存儲(chǔ)在哈希表中匠襟,cache的存儲(chǔ)方式是散列表钝侠,也可以說(shuō)是哈希表,通過(guò)哈希算法計(jì)算下標(biāo)來(lái)寫(xiě)入數(shù)據(jù)酸舍,所以并沒(méi)有順序帅韧。
  4. maskAndBuckets的mask存儲(chǔ)在前16位,buckets存儲(chǔ)在后44位啃勉,中間4位一直為0忽舟,中間的4位0是為了再快速查找方法時(shí)更快速的跳轉(zhuǎn)到最后一個(gè)下標(biāo),通過(guò)掩碼進(jìn)行計(jì)算璧亮。
  5. 對(duì)于容量
    1. 首次創(chuàng)建的容量是4
    2. 當(dāng)存儲(chǔ)的occupied+2大于容量的3/4時(shí)就會(huì)擴(kuò)容(這是考慮到多線程)
    3. 擴(kuò)容并不是在原來(lái)內(nèi)存的基礎(chǔ)上擴(kuò)展內(nèi)存萧诫,而是刪除舊空間,創(chuàng)建新空間枝嘶,因此每次擴(kuò)容都需要清除之前的緩存空間帘饶,清除緩存的sel和imp。
    4. 一次擴(kuò)容是原來(lái)容量的2倍群扶,容量最多是2的16次方
  6. 哈希算法
    1. sel與上mask就可以得到哈希地址
    2. 如果沖突了及刻,就需要調(diào)用哈希沖突算法
      1. 哈希沖突算法為i:i-1?mask
      2. 也就是向前移動(dòng)一位去存儲(chǔ),如果一直移動(dòng)到第一位仍然沖突竞阐,就從最后一位開(kāi)始插入
      3. 如果一直找到begin還在沖突則直接退出

4缴饭、 bits

從上文我們得知bits存儲(chǔ)了類(lèi)信息,有成員變量骆莹、屬性颗搂、實(shí)例方法、協(xié)議幕垦。那么具體是怎么存儲(chǔ)的呢丢氢,需要查看底層結(jié)構(gòu)

4.1 bits的認(rèn)識(shí)

可以看到bits只有一個(gè)作用,就是用它來(lái)獲取class_rw_t格式的數(shù)據(jù)先改,因此我們就需要分析class_rw_t疚察。

源碼:

struct class_data_bits_t {
    friend objc_class;

    // Values are the FAST_ flags above.
    uintptr_t bits;
private:
    //獲取數(shù)據(jù),為class_rw_t
    class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
}

4.2 class_rw_t

源碼:

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t witness;
#if SUPPORT_INDEXED_ISA
    uint16_t index;
#endif

    //屬性 ro或rwe仇奶,這是一個(gè)結(jié)構(gòu)體貌嫡,包含ro和rwe
    explicit_atomic<uintptr_t> ro_or_rw_ext;

    Class firstSubclass;
    Class nextSiblingClass;

private:
    using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t *, class_rw_ext_t *>;

    const ro_or_rw_ext_t get_ro_or_rwe() const {
        return ro_or_rw_ext_t{ro_or_rw_ext};
    }

    void set_ro_or_rwe(const class_ro_t *ro) {
        ro_or_rw_ext_t{ro}.storeAt(ro_or_rw_ext, memory_order_relaxed);
    }

    void set_ro_or_rwe(class_rw_ext_t *rwe, const class_ro_t *ro) {
        // the release barrier is so that the class_rw_ext_t::ro initialization
        // is visible to lockless readers
        rwe->ro = ro;
        ro_or_rw_ext_t{rwe}.storeAt(ro_or_rw_ext, memory_order_release);
    }

    class_rw_ext_t *extAlloc(const class_ro_t *ro, bool deep = false);

public:
    void setFlags(uint32_t set)
    {
        __c11_atomic_fetch_or((_Atomic(uint32_t) *)&flags, set, __ATOMIC_RELAXED);
    }

    void clearFlags(uint32_t clear) 
    {
        __c11_atomic_fetch_and((_Atomic(uint32_t) *)&flags, ~clear, __ATOMIC_RELAXED);
    }

    // set and clear must not overlap
    void changeFlags(uint32_t set, uint32_t clear) 
    {
        ASSERT((set & clear) == 0);

        uint32_t oldf, newf;
        do {
            oldf = flags;
            newf = (oldf | set) & ~clear;
        } while (!OSAtomicCompareAndSwap32Barrier(oldf, newf, (volatile int32_t *)&flags));
    }

    //得到rwe
    class_rw_ext_t *ext() const {
        return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>();
    }

    //開(kāi)辟rwe的空間
    class_rw_ext_t *extAllocIfNeeded() {
        auto v = get_ro_or_rwe();
        if (fastpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>();
        } else {
            return extAlloc(v.get<const class_ro_t *>());
        }
    }

    class_rw_ext_t *deepCopy(const class_ro_t *ro) {
        return extAlloc(ro, true);
    }

    //獲取ro
    const class_ro_t *ro() const {
        auto v = get_ro_or_rwe();
        //如果存在class_rw_ext_t,則從rwe中查找ro
        if (slowpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>()->ro;
        }
        //否則直接查找
        return v.get<const class_ro_t *>();
    }

    //第一次從內(nèi)存中加載給rw設(shè)置數(shù)據(jù)的時(shí)候,需要設(shè)置ro
    void set_ro(const class_ro_t *ro) {
        auto v = get_ro_or_rwe();
        //為什么有可能會(huì)有class_rw_ext_t?岛抄?别惦??夫椭?
        if (v.is<class_rw_ext_t *>()) {
            v.get<class_rw_ext_t *>()->ro = ro;
        } else {
            set_ro_or_rwe(ro);
        }
    }

    //獲取所有的方法列表的數(shù)組
    const method_array_t methods() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->methods;
        } else {
            return method_array_t{v.get<const class_ro_t *>()->baseMethods()};
        }
    }

    //獲取所有的屬性列表的數(shù)組
    const property_array_t properties() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->properties;
        } else {
            return property_array_t{v.get<const class_ro_t *>()->baseProperties};
        }
    }

    //獲取所有的協(xié)議列表的數(shù)組
    const protocol_array_t protocols() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->protocols;
        } else {
            return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols};
        }
    }
};

下面對(duì)結(jié)構(gòu)體的成員和函數(shù)進(jìn)行分析:

  1. ro_or_rw_ext是一個(gè)PointerUnion結(jié)構(gòu)體步咪,存儲(chǔ)有ro和rwe,這個(gè)結(jié)構(gòu)體提供了一些函數(shù)可以對(duì)存儲(chǔ)的ro和rwe進(jìn)行操作∫媛ィ可以看到這個(gè)結(jié)構(gòu)體提供了storeAt函數(shù)和get()函數(shù)。
using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t *, class_rw_ext_t *>;


template <class PT1, class PT2>
class PointerUnion {
    uintptr_t _value;

    static_assert(alignof(PT1) >= 2, "alignment requirement");
    static_assert(alignof(PT2) >= 2, "alignment requirement");

    struct IsPT1 {
      static const uintptr_t Num = 0;
    };
    struct IsPT2 {
      static const uintptr_t Num = 1;
    };
    template <typename T> struct UNION_DOESNT_CONTAIN_TYPE {};

    uintptr_t getPointer() const {
        return _value & ~1;
    }
    uintptr_t getTag() const {
        return _value & 1;
    }

public:
    explicit PointerUnion(const std::atomic<uintptr_t> &raw)
    : _value(raw.load(std::memory_order_relaxed))
    { }
    PointerUnion(PT1 t) : _value((uintptr_t)t) { }
    PointerUnion(PT2 t) : _value((uintptr_t)t | 1) { }

//存儲(chǔ)數(shù)據(jù)
    void storeAt(std::atomic<uintptr_t> &raw, std::memory_order order) const {
        raw.store(_value, order);
    }

    template <typename T>
    bool is() const {
        using Ty = typename PointerUnionTypeSelector<PT1, T, IsPT1,
            PointerUnionTypeSelector<PT2, T, IsPT2,
            UNION_DOESNT_CONTAIN_TYPE<T>>>::Return;
        return getTag() == Ty::Num;
    }

//得到某個(gè)數(shù)據(jù)
    template <typename T> T get() const {
      ASSERT(is<T>() && "Invalid accessor called");
      return reinterpret_cast<T>(getPointer());
    }

    template <typename T> T dyn_cast() const {
      if (is<T>())
        return get<T>();
      return T();
    }
};


  1. 提供了一些函數(shù)可以get和set成員ro_or_rw_ext点晴。下面是get和set的函數(shù)
const ro_or_rw_ext_t get_ro_or_rwe() const {
        return ro_or_rw_ext_t{ro_or_rw_ext};
    }

    void set_ro_or_rwe(const class_ro_t *ro) {
        ro_or_rw_ext_t{ro}.storeAt(ro_or_rw_ext, memory_order_relaxed);
    }

    void set_ro_or_rwe(class_rw_ext_t *rwe, const class_ro_t *ro) {
        // the release barrier is so that the class_rw_ext_t::ro initialization
        // is visible to lockless readers
        rwe->ro = ro;
        ro_or_rw_ext_t{rwe}.storeAt(ro_or_rw_ext, memory_order_release);
    }
  1. 提供函數(shù)ext()獲取class_rw_ext_t

調(diào)用了私有的函數(shù)get_ro_or_rwe()并通過(guò)結(jié)構(gòu)體來(lái)獲取其中的class_rw_ext_t感凤。

//得到rwe
    class_rw_ext_t *ext() const {
        return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>();
    }
  1. 提供函數(shù)ro()獲取class_ro_t

會(huì)先判斷是否有rwe,如果存在粒督,先到rwe中查詢(xún)r(jià)o陪竿,如果不存在,就直接獲取rw中的ro屠橄,這個(gè)順序不要搞混了族跛。(雖然還并不清楚蘋(píng)果為什么要這樣做)

const class_ro_t *ro() const {
        auto v = get_ro_or_rwe();
        //如果存在class_rw_ext_t,則從rwe中查找ro
        if (slowpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>()->ro;
        }
        //否則直接查找
        return v.get<const class_ro_t *>();
    }
  1. 還需要注意一個(gè)函數(shù)extAllocIfNeeded(),它是用來(lái)開(kāi)辟rwe的空間锐墙,當(dāng)給類(lèi)附著分類(lèi)數(shù)據(jù)時(shí)礁哄,或運(yùn)行時(shí)創(chuàng)建的方法、屬性時(shí)會(huì)使用它來(lái)創(chuàng)建rwe
//開(kāi)辟rwe的空間
    class_rw_ext_t *extAllocIfNeeded() {
        auto v = get_ro_or_rwe();
        if (fastpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>();
        } else {
            return extAlloc(v.get<const class_ro_t *>());
        }
    }
  1. 獲取方法列表的數(shù)組溪北、屬性列表的數(shù)組桐绒、協(xié)議列表的數(shù)組

這里的順序也要注意,可以看到也是先判斷rwe中的methods之拨,如果沒(méi)有rwe茉继,才獲取ro中的methods

//獲取所有的方法列表的數(shù)組
    const method_array_t methods() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->methods;
        } else {
            return method_array_t{v.get<const class_ro_t *>()->baseMethods()};
        }
    }

    //獲取所有的屬性列表的數(shù)組
    const property_array_t properties() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->properties;
        } else {
            return property_array_t{v.get<const class_ro_t *>()->baseProperties};
        }
    }

    //獲取所有的協(xié)議列表的數(shù)組
    const protocol_array_t protocols() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->protocols;
        } else {
            return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols};
        }
    }

注意:

  1. rw中并沒(méi)有給提供獲取成員變量的函數(shù),只可以獲取方法蚀乔、屬性和協(xié)議
  2. 關(guān)于類(lèi)信息的成員只有ro_or_rw_ext烁竭,這也說(shuō)明rw本身也不存儲(chǔ)數(shù)據(jù),而是存儲(chǔ)在ro和rwe中
  3. rw表示可以讀寫(xiě)吉挣,可以獲取也可以存儲(chǔ)數(shù)據(jù)
  4. 對(duì)于所有的數(shù)據(jù)都需要先判斷是否存在rwe派撕,如果存在就先獲取rwe中的數(shù)據(jù),如果不存在再獲取ro中的數(shù)據(jù)

4.3 class_ro_t的認(rèn)識(shí)

源碼:

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    //這個(gè)是干什么的
    const uint8_t * ivarLayout;
    
    const char * name;
    //方法听想、協(xié)議腥刹、屬性
    method_list_t * baseMethodList;//基礎(chǔ)方法列表
    protocol_list_t * baseProtocols;//基礎(chǔ)協(xié)議列表
    const ivar_list_t * ivars;//成員變量列表,這里變量沒(méi)有寫(xiě)base汉买,也可以看出這個(gè)是不變的

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;//基礎(chǔ)屬性列表

    // This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
    _objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];

    _objc_swiftMetadataInitializer swiftMetadataInitializer() const {
        if (flags & RO_HAS_SWIFT_INITIALIZER) {
            return _swiftMetadataInitializer_NEVER_USE[0];
        } else {
            return nil;
        }
    }

    method_list_t *baseMethods() const {
        return baseMethodList;
    }

    class_ro_t *duplicate() const {
        if (flags & RO_HAS_SWIFT_INITIALIZER) {
            size_t size = sizeof(*this) + sizeof(_swiftMetadataInitializer_NEVER_USE[0]);
            class_ro_t *ro = (class_ro_t *)memdup(this, size);
            ro->_swiftMetadataInitializer_NEVER_USE[0] = this->_swiftMetadataInitializer_NEVER_USE[0];
            return ro;
        } else {
            size_t size = sizeof(*this);
            class_ro_t *ro = (class_ro_t *)memdup(this, size);
            return ro;
        }
    }
};

說(shuō)明:

  • 它有基礎(chǔ)方法列表衔峰、基礎(chǔ)協(xié)議列表、成員變量列表、基礎(chǔ)屬性列表
  • 為什么要提基礎(chǔ)二字垫卤,這是因?yàn)楫?dāng)類(lèi)加載進(jìn)內(nèi)存時(shí)的類(lèi)的數(shù)據(jù)就存儲(chǔ)在這里威彰,而至于分類(lèi)附著到類(lèi)上的數(shù)據(jù)以及運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建的數(shù)據(jù)都不存儲(chǔ)在這里,而是存儲(chǔ)在rwe中穴肘。
  • 也就是說(shuō)ro中只存儲(chǔ)類(lèi)本身的數(shù)據(jù)
  • ro中包含有成員變量列表歇盼,因?yàn)樗淮鎯?chǔ)在ro中,而不存在與rwe中评抚,因此它是只讀的豹缀,不可寫(xiě)入。
  • 我們知道在rw中并沒(méi)有獲取成員變量的入口函數(shù)慨代,所以我們只能通過(guò)ro來(lái)間接獲取

4.4 class_rw_ext_t

源碼:

/*
 這里包含了ro
 新增的只有方法邢笙、屬性、協(xié)議侍匙,沒(méi)有變量
 這里存放的是所有的氮惯,包含目標(biāo)類(lèi)的
 */
struct class_rw_ext_t {
    const class_ro_t *ro;
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    char *demangledName;
    uint32_t version;
};

說(shuō)明:

  • 可以看出來(lái)在rwe中也包含有ro,也就是說(shuō)rwe并不單單只有分類(lèi)或運(yùn)行時(shí)新增的數(shù)據(jù)想暗,也包含類(lèi)的基礎(chǔ)數(shù)據(jù)妇汗。
  • 同時(shí)我們?cè)趓w的獲取方法列表、屬性列表说莫、協(xié)議列表的函數(shù)中可以看到杨箭,其實(shí)在rwe的方法列表、屬性列表储狭、協(xié)議列表中也包含有ro的基礎(chǔ)列表告唆。這個(gè)其實(shí)在后面分析類(lèi)的加載過(guò)程的時(shí)候更能清晰的看出來(lái)會(huì)將ro中的數(shù)據(jù)直接copy到rwe中
  • 也就是說(shuō)ro的數(shù)據(jù)其實(shí)在三處地方可以獲取到,1)直接通過(guò)rw->ro->methods晶密;2)通過(guò)rw->rwe->ro->methods;3)rw->rwe->methods

4.5 列表數(shù)據(jù)

后面會(huì)專(zhuān)門(mén)分析協(xié)議擒悬,此處不分析協(xié)議了。

4.5.1 列表數(shù)組

列表數(shù)組格式一樣稻艰,只以方法列表數(shù)組為例懂牧。方法列表為method_list_t,方法為method_t尊勿。
源碼:

class method_array_t : 
    public list_array_tt<method_t, method_list_t> 
{
    typedef list_array_tt<method_t, method_list_t> Super;

 public:
    method_array_t() : Super() { }
    method_array_t(method_list_t *l) : Super(l) { }

    method_list_t * const *beginCategoryMethodLists() const {
        return beginLists();
    }
    
    method_list_t * const *endCategoryMethodLists(Class cls) const;

    method_array_t duplicate() {
        return Super::duplicate<method_array_t>();
    }
};

4.5.2 方法列表僧凤、成員變量列表、屬性列表

都是以entsize_list_tt結(jié)構(gòu)體存儲(chǔ)的元扔,這個(gè)結(jié)構(gòu)體的樣式后面再分析

// Two bits of entsize are used for fixup markers.
//entsize的兩位用作固定標(biāo)記
struct method_list_t : entsize_list_tt<method_t, method_list_t, 0x3> {
    bool isUniqued() const;
    bool isFixedUp() const;
    void setFixedUp();

    //返回某個(gè)方法的下標(biāo)
    uint32_t indexOfMethod(const method_t *meth) const {
        uint32_t i = 
            (uint32_t)(((uintptr_t)meth - (uintptr_t)this) / entsize());
        ASSERT(i < count);
        return i;
    }
};

struct ivar_list_t : entsize_list_tt<ivar_t, ivar_list_t, 0> {
    //增加了一個(gè)功能躯保,就是判斷是否存在這個(gè)成員變量
    bool containsIvar(Ivar ivar) const {
        return (ivar >= (Ivar)&*begin()  &&  ivar < (Ivar)&*end());
    }
};

struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
};

4.5.3 方法、屬性澎语、成員變量結(jié)構(gòu)體

4.5.3.1 method_t

源碼:

/*
 1途事、方法選擇器
 2验懊、方法類(lèi)型
 3、函數(shù)指針
 */
struct method_t {
    SEL name;
    const char *types;
    MethodListIMP imp;

    //這種寫(xiě)法方式?jīng)]看懂
    //但是比較明顯的是左<右尸变,就返回YES义图,這是基本的一個(gè)排序操作
    struct SortBySELAddress :
        public std::binary_function<const method_t&,
                                    const method_t&, bool>
    {
        bool operator() (const method_t& lhs,
                         const method_t& rhs)
        { return lhs.name < rhs.name; }
    };
};

說(shuō)明:

  • 一個(gè)方法中包含有方法選擇器sel、方法類(lèi)型召烂、函數(shù)指針imp
  • 還提供了一個(gè)函數(shù)用以排序碱工。這個(gè)將在類(lèi)的加載時(shí)會(huì)用到,判斷兩個(gè)方法的順序奏夫,這里先記一下有這么個(gè)玩意
4.5.3.2 ivar_t

源碼:

struct ivar_t {
#if __x86_64__
    // *offset was originally 64-bit on some x86_64 platforms.
    // We read and write only 32 bits of it.
    // Some metadata provides all 64 bits. This is harmless for unsigned 
    // little-endian values.
    // Some code uses all 64 bits. class_addIvar() over-allocates the 
    // offset for their benefit.
#endif
    int32_t *offset;//偏移量怕篷,這個(gè)是干什么的
    const char *name;//名稱(chēng)
    const char *type;//類(lèi)型
    // alignment is sometimes -1; use alignment() instead
    uint32_t alignment_raw;//多少字節(jié)對(duì)齊
    uint32_t size;//大小

    //獲取當(dāng)前的對(duì)齊字節(jié)數(shù)
    uint32_t alignment() const {
        //8字節(jié)對(duì)齊
        if (alignment_raw == ~(uint32_t)0) return 1U << WORD_SHIFT;
        //否則按照設(shè)置的字節(jié)進(jìn)行對(duì)齊
        return 1 << alignment_raw;
    }
};

說(shuō)明:

  • 成員變量只有變量名稱(chēng)和類(lèi)型,沒(méi)什么額外的數(shù)據(jù)
4.5.3.3 property_t*

源碼:

//名稱(chēng)和attributes
//以后要區(qū)分property和attributes酗昼,屬性是有自己的一些屬性的比如copy/strong
struct property_t {
    const char *name;
    const char *attributes;
};

說(shuō)明:

  • 屬性除了屬性名稱(chēng)匙头,就是attributes,它存儲(chǔ)的就是比如copy仔雷、strong等等。

4.6 驗(yàn)證

通過(guò)LLDB查看bits中的所有類(lèi)信息與我們所定義的類(lèi)的信息是否是一致的舔示。

通過(guò)指針偏移得到bits數(shù)據(jù)碟婆,再分別得到rw中的rwe和ro,通過(guò)這種方式就可以獲取到所有的方法列表惕稻、協(xié)議列表竖共、屬性列表、成員變量列表俺祠。

4.6.1 獲取bits

  • isa屬性: 繼承自objc_object的isa公给,占8個(gè)字節(jié)

  • Class superclass: 是Class類(lèi)型,是一個(gè)指針蜘渣,占8個(gè)字節(jié)

  • cache

struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    explicit_atomic<struct bucket_t *> _buckets; // 是一個(gè)結(jié)構(gòu)體指針類(lèi)型淌铐,占8字節(jié)
    explicit_atomic<mask_t> _mask; //是mask_t 類(lèi)型,而 mask_t 是 unsigned int 的別名蔫缸,占4字節(jié)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    explicit_atomic<uintptr_t> _maskAndBuckets; //是指針腿准,占8字節(jié)
    mask_t _mask_unused; //是mask_t 類(lèi)型,而 mask_t 是 uint32_t 類(lèi)型定義的別名拾碌,占4字節(jié)
    
#if __LP64__
    uint16_t _flags;  //是uint16_t類(lèi)型吐葱,uint16_t是 unsigned short 的別名,占 2個(gè)字節(jié)
#endif
    uint16_t _occupied; //是uint16_t類(lèi)型校翔,uint16_t是 unsigned short 的別名弟跑,占 2個(gè)字節(jié)

這里只看真機(jī)64位:

  • _maskAndBuckets 是uintptr_t類(lèi)型,它是一個(gè)指針防症,占8字節(jié)

  • _mask_unused 是mask_t 類(lèi)型孟辑,而 mask_t 占4字節(jié)

  • _flags是uint16_t類(lèi)型哎甲,uint16_t是 unsigned short 的別名,兩個(gè)字節(jié)

  • occupied的大小也是uint16_t類(lèi)型扑浸,uint16_t是 unsigned short 的別名烧给,兩個(gè)字節(jié)

計(jì)算:

  • 所以最后計(jì)算出cache類(lèi)的內(nèi)存大小 = 12 + 2 + 2 = 16字節(jié)

結(jié)論: 從首地址需要平移32個(gè)字節(jié)大小得到bits

4.6.2 開(kāi)始獲取

【第一步】通過(guò)指針偏移得到bits

得到bits.png

說(shuō)明:

  • 因?yàn)樾枰?2個(gè)字節(jié),而這里是16進(jìn)制的數(shù)值,所以需要從0x100002250變?yōu)?x100002270。

【第二步】得到rw

得到rw.png

說(shuō)明:

  • p *30->data()就得到了bits.data()數(shù)據(jù)(帶 *號(hào)需曾,所以就需要通過(guò)3->這種來(lái)獲取竿滨,指針函數(shù)就用->)
  • p *$4就是這個(gè)數(shù)據(jù)的所有對(duì)象信息
  • 所有的信息都存儲(chǔ)在ro_or_rw里

【第三步】獲取方法列表

方法列表.png
  • p $4.methods()得到方法信息
  • p $5.list得到方法列表
  • p *$6這里獲取到的是方法列表的第一個(gè)數(shù)據(jù)。所以是第一個(gè)方法
  • 也可以通過(guò)p $7.get(0)來(lái)獲取第一個(gè)方法

【第四步】獲取屬性列表

過(guò)程與方法列表一樣迹鹅,而且在上文已經(jīng)詳細(xì)查看了rw的底層實(shí)現(xiàn),所以直接看過(guò)程,不再贅言了巫财。

屬性列表.png

【第五步】獲取成員變量列表

可以看到需要先獲取ro,之后通過(guò)ro來(lái)獲取成員變量列表哩陕,因?yàn)閞w中并沒(méi)有提供獲取成員變量的入口函數(shù)

成員變量列表.png

4.7 總結(jié)

  1. OC中類(lèi)的底層結(jié)構(gòu)是objc_class平项,繼承自objc_object結(jié)構(gòu)體
  2. objc_class中的bits存儲(chǔ)了屬性、方法悍及、協(xié)議闽瓢、成員變量,其中成員變量并不直接存儲(chǔ)在class_rw_t中心赶,而是存儲(chǔ)在class_ro_t中扣讼,是干凈內(nèi)存
  3. class_rw_t可以得到類(lèi)的所有信息,包括ro和rwe
  4. rw中獲取數(shù)據(jù)缨叫,先判斷是否存在rwe椭符,若存在rwe則通過(guò)rwe獲取,否則通過(guò)ro獲取
  5. ro是干凈內(nèi)存耻姥,類(lèi)的加載時(shí)類(lèi)本身的數(shù)據(jù)存放在ro中
  6. rwe是臟內(nèi)存销钝,運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建成員或分類(lèi)中的成員都在rwe,包括屬性琐簇、方法曙搬、協(xié)議。
  7. rwe中的屬性鸽嫂、方法纵装、協(xié)議列表也包括ro中的數(shù)據(jù)
  8. rwe結(jié)構(gòu)體中包含有ro

5、簡(jiǎn)單總結(jié)

  1. OC中類(lèi)的底層結(jié)構(gòu)是objc_class据某,繼承自objc_object結(jié)構(gòu)體
  2. objc_class結(jié)構(gòu)體中中包括四個(gè)屬性,isa橡娄、superClass、cache癣籽、bits
  3. isa繼承自objc_object挽唉,包含有元類(lèi)信息
  4. superClass也是類(lèi)結(jié)構(gòu)滤祖,表示父類(lèi)
  5. cache是緩存的方法列表,通過(guò)哈希表存儲(chǔ)sel和imp瓶籽,在進(jìn)行快速消息發(fā)送時(shí)在cache中通過(guò)sel查找imp
  6. cache的存儲(chǔ)和獲取都需要通過(guò)哈希算法來(lái)計(jì)算
  7. bits存儲(chǔ)有類(lèi)信息匠童,包含屬性、方法塑顺、協(xié)議汤求、成員變量,bits的數(shù)據(jù)是rw严拒,但是分成兩種類(lèi)型存儲(chǔ)扬绪,一種是干凈內(nèi)存ro只存儲(chǔ)類(lèi)本身的數(shù)據(jù),一種是臟內(nèi)存rwe存儲(chǔ)分類(lèi)數(shù)據(jù)和運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建的數(shù)據(jù)
  8. 元類(lèi)是由編譯期定義和創(chuàng)建的裤唠,用來(lái)管理類(lèi)挤牛,是類(lèi)對(duì)象的類(lèi)。類(lèi)方法在元類(lèi)中以對(duì)象方法的姿態(tài)存在种蘸。
  9. 元類(lèi)本身我們無(wú)法看到墓赴,無(wú)法直接使用,可以通過(guò)類(lèi)的isa查看元類(lèi)信息
  10. 繼承關(guān)系是類(lèi)和元類(lèi)的關(guān)系航瞭,不是對(duì)象的關(guān)系诫硕,根元類(lèi)繼承自NSObject類(lèi),NSObject類(lèi)的父類(lèi)是nil沧奴,也就是沒(méi)有父類(lèi),NSObject是萬(wàn)物起源长窄。
  11. 對(duì)象的isa指向類(lèi)滔吠,類(lèi)的ISA指向元類(lèi),元類(lèi)的isa指向根元類(lèi)挠日,根元類(lèi)的isa指向自己
  12. NSObject類(lèi)的isa也指向根元類(lèi)

通過(guò)LLDB驗(yàn)證過(guò)程中使用到了指針偏移疮绷,有疑惑的地方可以看指針偏移原理分析鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者嚣潜。
  • 序言:七十年代末冬骚,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子懂算,更是在濱河造成了極大的恐慌只冻,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件计技,死亡現(xiàn)場(chǎng)離奇詭異喜德,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)垮媒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)舍悯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)航棱,“玉大人,你說(shuō)我怎么就攤上這事萌衬∫迹” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵秕豫,是天一觀的道長(zhǎng)朴艰。 經(jīng)常有香客問(wèn)我,道長(zhǎng)馁蒂,這世上最難降的妖魔是什么呵晚? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮沫屡,結(jié)果婚禮上饵隙,老公的妹妹穿的比我還像新娘。我一直安慰自己沮脖,他們只是感情好金矛,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著勺届,像睡著了一般驶俊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上免姿,一...
    開(kāi)封第一講書(shū)人閱讀 49,950評(píng)論 1 291
  • 那天饼酿,我揣著相機(jī)與錄音,去河邊找鬼胚膊。 笑死故俐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的紊婉。 我是一名探鬼主播药版,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼喻犁!你這毒婦竟也來(lái)了槽片?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤肢础,失蹤者是張志新(化名)和其女友劉穎还栓,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體传轰,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蝙云,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了路召。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片勃刨。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡波材,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出身隐,到底是詐尸還是另有隱情廷区,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布贾铝,位于F島的核電站隙轻,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏垢揩。R本人自食惡果不足惜玖绿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望叁巨。 院中可真熱鬧斑匪,春花似錦、人聲如沸锋勺。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)庶橱。三九已至贮勃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間苏章,已是汗流浹背寂嘉。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留枫绅,地道東北人泉孩。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像撑瞧,于是被迫代替她去往敵國(guó)和親棵譬。 傳聞我的和親對(duì)象是個(gè)殘疾皇子显蝌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容