整體分析類(lèi)在底層的結(jié)構(gòu)斟赚,以及對(duì)類(lèi)的操作在底層是如何實(shí)現(xiàn)的鹿寻,重點(diǎn)包括cache和bits的分析届氢。
主要內(nèi)容:
- 類(lèi)的底層結(jié)構(gòu)objc_class的認(rèn)識(shí),及objc_class與objc_object的關(guān)系
- isa的認(rèn)識(shí),isa的走向以及類(lèi)的繼承關(guān)系
- cache的詳細(xì)解讀
- 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é)
- objc_class是繼承自objc_object疗锐,因此類(lèi)本身也是一個(gè)類(lèi)對(duì)象坊谁,類(lèi)是元類(lèi)的對(duì)象
- objc_object作為對(duì)象的底層結(jié)構(gòu)體有一個(gè)isa,說(shuō)明對(duì)象的底層結(jié)構(gòu)只包括isa滑臊。
- 所有的對(duì)象口芍、類(lèi)、元類(lèi)雇卷、協(xié)議都有isa成員鬓椭,充分說(shuō)明在面向?qū)ο蟮氖澜缋锶f(wàn)事萬(wàn)物皆對(duì)象
- 所有的對(duì)象底層是以objc_object為模板創(chuàng)建的結(jié)構(gòu)體,所有的類(lèi)的底層是以objc_class為模板創(chuàng)建的結(jié)構(gòu)體
- 元類(lèi)也有isa关划,因?yàn)樗灿凶约旱念?lèi)就是根元類(lèi)小染,根元類(lèi)的類(lèi)是他自己,所以根元類(lèi)也有自己的isa贮折。
- 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)系圖:
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ù)圖可以看出:
- 對(duì)象的isa指向類(lèi)
- 類(lèi)的isa指向元類(lèi)
- 元類(lèi)的isa指向根元類(lèi)
- NSObject類(lèi)也指向根元類(lèi)
- 根元類(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)
說(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)
說(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)
說(shuō)明:
- 查看類(lèi)的isa规丽,發(fā)現(xiàn)是NSObject蒲牧,其實(shí)此時(shí)是根元類(lèi),
4赌莺、驗(yàn)證根元類(lèi)的isa
說(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é)
- 對(duì)象的isa指向類(lèi)菠净,類(lèi)指向元類(lèi)禁舷,元類(lèi)指向根元類(lèi),根元類(lèi)指向自己
- NSObject類(lèi)指向NSObject根元類(lèi)毅往,根元類(lèi)指向自己
- 繼承關(guān)系指的是類(lèi)或者元類(lèi)的關(guān)系牵咙,而不是對(duì)象的關(guān)系
- 對(duì)象繼承于類(lèi),元類(lèi)也有繼承關(guān)系
- 一定要注意根元類(lèi)繼承于NSObject
- 最后的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é)
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é)果:
疑問(wèn):
- _mask是什么姐扮?
- _occupied 是什么絮供?
- 為什么隨著方法調(diào)用的增多,其打印的occupied 和 mask會(huì)變化茶敏?
- 為什么隨著方法調(diào)用的增多壤靶,其打印的occupied 和 mask會(huì)變化?
- bucket數(shù)據(jù)為什么會(huì)有丟失的情況睡榆?萍肆,例如2-7中,只有say3胀屿、say4方法有函數(shù)指針
- 2-7中say3塘揣、say4的打印順序?yàn)槭裁词莝ay4先打印,say3后打印宿崭,且還是挨著的亲铡,即順序有問(wèn)題?
- 打印的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()
- 我們?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)為自增
- 這里可以說(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)用
3.3.1.4 全局搜索insert()方法知允,發(fā)現(xiàn)只有cache_fill方法中的調(diào)用符合
3.3.1.5 全局搜索cache_fill,發(fā)現(xiàn)在寫(xiě)入之前叙谨,還有一步操作温鸽,即cache讀取,即查找sel-imp手负,如下所示
- 在這里并沒(méi)有找到如何去調(diào)用的涤垫,可能是系統(tǒng)沒(méi)有給我們展示
- 但是系統(tǒng)說(shuō)明了它內(nèi)部執(zhí)行的流程,是先讀緩存虫溜,之后再寫(xiě)緩存
3.3.1.6 總結(jié):
- 在發(fā)送消息后雹姊,緩存需要先被讀取
- 如果緩存中不存在,則需要將得到的sel和imp寫(xiě)入緩存
- 寫(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)辟空間
- 得到緩存量的占用量祈惶。 這是一個(gè)臨時(shí)變量雕旨,假如添加成功之后的一個(gè)占用量,用作后面是否進(jìn)行擴(kuò)容的判斷捧请。
- 得到當(dāng)前總的緩存量凡涩。 這是當(dāng)前已有的緩存量,主要用來(lái)判斷這一次是否還需要進(jìn)行擴(kuò)容疹蛉。
- 首次開(kāi)辟空間活箕。
- 判斷容量為空,則進(jìn)行初始化可款,首次開(kāi)辟空間
- 先獲取到開(kāi)辟空間的容量
- 首次的空間是4
- 之后調(diào)用reallocate()函數(shù)進(jìn)行開(kāi)辟
- 傳入的是false讹蘑,不會(huì)刪除舊空間,因?yàn)槭鞘状伍_(kāi)辟
- 直接存儲(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)橐枰紤]多線程的影響
- 擴(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除舊空間。
源碼:
說(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ī)磺箕。
3.3.5 cache_collect_free()函數(shù)分析
該方法對(duì)數(shù)據(jù)進(jìn)行垃圾回收
- 垃圾回收,清理舊的bucket
- 擴(kuò)容后不需要移植緩存信息抛虫,這樣可以提高性能松靡,因?yàn)楫?dāng)調(diào)用的方法越多,記錄的會(huì)越多建椰,這樣每次擴(kuò)容移植的信息就越多雕欺,性能會(huì)差,同時(shí)還可以避免一定程度的哈希沖突棉姐。
3.3.6 _garbage_make_room函數(shù)
- 創(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ǔ)格式:
- maskAndBuckets總共64位
- mask占有高16位
- buckets占有低44位
- 中間4位對(duì)于消息發(fā)送會(huì)更有優(yōu)勢(shì)荐糜,在查詢(xún)緩存時(shí)會(huì)用到(這里先記一下,后面在消息發(fā)送的分析中會(huì)用到)
3.4.2 掩碼數(shù)據(jù)
- 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ì)算:
- 傳入的值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:
- bucketsMask 1<<44 -1
- 1<<44 燃箭。 在第45位為1冲呢,后44為0
- 1<<44 -1。 減去1招狸,就是后44位都為1了
- 所以跟bucketsMask相與就得到后44位的buckets
mask:
- 向右平移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)建:
方法調(diào)用:
分析過(guò)程:
- 跟之前一樣,就是查找這個(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é)
- cache存儲(chǔ)了sel和imp,可以進(jìn)行快速消息發(fā)送叹誉,sel-imp存儲(chǔ)在哈希表中鸯两,通過(guò)哈希算法來(lái)計(jì)算下標(biāo)。
- cache中的maskAndBuckets中包含有buckets长豁,而buckets包含有多個(gè)bucket钧唐,每個(gè)bucket存儲(chǔ)了一個(gè)sel和imp的鍵值對(duì)。
- maskAndBuckets存儲(chǔ)在哈希表中匠襟,cache的存儲(chǔ)方式是散列表钝侠,也可以說(shuō)是哈希表,通過(guò)哈希算法計(jì)算下標(biāo)來(lái)寫(xiě)入數(shù)據(jù)酸舍,所以并沒(méi)有順序帅韧。
- maskAndBuckets的mask存儲(chǔ)在前16位,buckets存儲(chǔ)在后44位啃勉,中間4位一直為0忽舟,中間的4位0是為了再快速查找方法時(shí)更快速的跳轉(zhuǎn)到最后一個(gè)下標(biāo),通過(guò)掩碼進(jìn)行計(jì)算璧亮。
- 對(duì)于容量
- 首次創(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ò)容都需要清除之前的緩存空間帘饶,清除緩存的sel和imp。
- 一次擴(kuò)容是原來(lái)容量的2倍群扶,容量最多是2的16次方
- 哈希算法
- sel與上mask就可以得到哈希地址
- 如果沖突了及刻,就需要調(diào)用哈希沖突算法
- 哈希沖突算法為i:i-1?mask
- 也就是向前移動(dòng)一位去存儲(chǔ),如果一直移動(dòng)到第一位仍然沖突竞阐,就從最后一位開(kāi)始插入
- 如果一直找到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)行分析:
- 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();
}
};
- 提供了一些函數(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);
}
- 提供函數(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 *>();
}
- 提供函數(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 *>();
}
- 還需要注意一個(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 *>());
}
}
- 獲取方法列表的數(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};
}
}
注意:
- rw中并沒(méi)有給提供獲取成員變量的函數(shù),只可以獲取方法蚀乔、屬性和協(xié)議
- 關(guān)于類(lèi)信息的成員只有ro_or_rw_ext烁竭,這也說(shuō)明rw本身也不存儲(chǔ)數(shù)據(jù),而是存儲(chǔ)在ro和rwe中
- rw表示可以讀寫(xiě)吉挣,可以獲取也可以存儲(chǔ)數(shù)據(jù)
- 對(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
說(shuō)明:
- 因?yàn)樾枰?2個(gè)字節(jié),而這里是16進(jìn)制的數(shù)值,所以需要從0x100002250變?yōu)?x100002270。
【第二步】得到rw
說(shuō)明:
- p *
3->這種來(lái)獲取竿滨,指針函數(shù)就用->)
- p *$4就是這個(gè)數(shù)據(jù)的所有對(duì)象信息
- 所有的信息都存儲(chǔ)在ro_or_rw里
【第三步】獲取方法列表
- 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ò)程,不再贅言了巫财。
【第五步】獲取成員變量列表
可以看到需要先獲取ro,之后通過(guò)ro來(lái)獲取成員變量列表哩陕,因?yàn)閞w中并沒(méi)有提供獲取成員變量的入口函數(shù)
4.7 總結(jié)
- OC中類(lèi)的底層結(jié)構(gòu)是objc_class平项,繼承自objc_object結(jié)構(gòu)體
- objc_class中的bits存儲(chǔ)了屬性、方法悍及、協(xié)議闽瓢、成員變量,其中成員變量并不直接存儲(chǔ)在class_rw_t中心赶,而是存儲(chǔ)在class_ro_t中扣讼,是干凈內(nèi)存
- class_rw_t可以得到類(lèi)的所有信息,包括ro和rwe
- rw中獲取數(shù)據(jù)缨叫,先判斷是否存在rwe椭符,若存在rwe則通過(guò)rwe獲取,否則通過(guò)ro獲取
- ro是干凈內(nèi)存耻姥,類(lèi)的加載時(shí)類(lèi)本身的數(shù)據(jù)存放在ro中
- rwe是臟內(nèi)存销钝,運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建成員或分類(lèi)中的成員都在rwe,包括屬性琐簇、方法曙搬、協(xié)議。
- rwe中的屬性鸽嫂、方法纵装、協(xié)議列表也包括ro中的數(shù)據(jù)
- rwe結(jié)構(gòu)體中包含有ro
5、簡(jiǎn)單總結(jié)
- OC中類(lèi)的底層結(jié)構(gòu)是objc_class据某,繼承自objc_object結(jié)構(gòu)體
- objc_class結(jié)構(gòu)體中中包括四個(gè)屬性,isa橡娄、superClass、cache癣籽、bits
- isa繼承自objc_object挽唉,包含有元類(lèi)信息
- superClass也是類(lèi)結(jié)構(gòu)滤祖,表示父類(lèi)
- cache是緩存的方法列表,通過(guò)哈希表存儲(chǔ)sel和imp瓶籽,在進(jìn)行快速消息發(fā)送時(shí)在cache中通過(guò)sel查找imp
- cache的存儲(chǔ)和獲取都需要通過(guò)哈希算法來(lái)計(jì)算
- 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ù)
- 元類(lèi)是由編譯期定義和創(chuàng)建的裤唠,用來(lái)管理類(lèi)挤牛,是類(lèi)對(duì)象的類(lèi)。類(lèi)方法在元類(lèi)中以對(duì)象方法的姿態(tài)存在种蘸。
- 元類(lèi)本身我們無(wú)法看到墓赴,無(wú)法直接使用,可以通過(guò)類(lèi)的isa查看元類(lèi)信息
- 繼承關(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)物起源长窄。
- 對(duì)象的isa指向類(lèi)滔吠,類(lèi)的ISA指向元類(lèi),元類(lèi)的isa指向根元類(lèi)挠日,根元類(lèi)的isa指向自己
- NSObject類(lèi)的isa也指向根元類(lèi)
通過(guò)LLDB驗(yàn)證過(guò)程中使用到了指針偏移疮绷,有疑惑的地方可以看指針偏移原理分析鏈接