本文的主要目的是分析 類 & 類的結(jié)構(gòu),整篇都是圍繞一個(gè)類展開的一些探索
類的分析
類的分析主要是分析 isa的走向 以及 繼承關(guān)系
準(zhǔn)備工作
定義兩個(gè)類
- 繼承自
NSObject的類HTPerson
@interface HTPerson : NSObject
{
NSString *hobby;
}
@property (nonatomic, copy) NSString *name;
- (void)sayHello;
+ (void)sayBye;
@end
@implementation HTPerson
- (void)sayHello
{}
+ (void)sayBye
{}
@end
- 繼承自
HTPerson的類HTTeacher
@interface HTTeacher : HTPerson
@end
@implementation HTTeacher
@end
- 在
main中分別用兩個(gè)定義兩個(gè)對(duì)象:person & teacher
int main(int argc, const char * argv[]) {
@autoreleasepool {
HTPerson *person = [[HTPerson alloc] init];
HTTeacher *teacher = [[HTTeacher alloc] init];
NSLog(@"person:%@======teacher:%@", person, teacher);
}
return 0;
}
元類
首先,我們先通過一個(gè)案例的lldb調(diào)試先引入元類
- 在main中
HTTeacher部分加一個(gè)斷點(diǎn),運(yùn)行程序 - 開啟lldb調(diào)試,調(diào)試的過程如下圖所示


根據(jù)調(diào)試過程,我們產(chǎn)生了一個(gè)疑問:為什么圖中的p/x 0x011d80010000828d & 0x00007ffffffffff8ULL 與 p/x 0x0000000100008260 & 0x00007ffffffffff8ULL 中的類信息打印出來都是HTPerson?
-
0x011d80010000828d是person對(duì)象的isa指針地址,其&后得到的結(jié)果是創(chuàng)建person的類HTPerson -
0x0000000100008260是isa中獲取的類信息所指的類的isa的指針地址,即HTPerson類的類的isa指針地址,在Apple中,我們簡(jiǎn)稱HTPerson類的類為元類 - 所以,兩個(gè)打印都是
HTPerson的根本原因就是因?yàn)?code>元類導(dǎo)致的
元類的說明
下面來解釋什么是元類,主要有以下幾點(diǎn)說明:
- 我們都知道
對(duì)象的isa 是指向類,類其實(shí)也是一個(gè)對(duì)象,可以稱為類對(duì)象,其isa的位域指向蘋果定義的元類 -
元類是系統(tǒng)給的,其定義和創(chuàng)建都是由編譯器完成,在這個(gè)過程中,類的歸屬來自于元類 -
元類是類對(duì)象的類,每個(gè)類都有一個(gè)獨(dú)一無二的元類用來存儲(chǔ)類方法的相關(guān)信息。 -
元類本身是沒有名稱的,由于與類相關(guān)聯(lián),所以使用了同類名一樣的名稱
下面通過lldb命令來探索元類的走向,也就是isa的走位,如下圖所示,可以得出一個(gè)關(guān)系鏈:對(duì)象 --> 類 --> 元類 --> NSObject的元類, NSObject的元類 指向自身

總結(jié)
從圖中可以看出
-
對(duì)象的isa指向類(也可稱為類對(duì)象) -
類的isa指向元類 -
元類的isa指向根元類,即NSObject的元類 -
根元類的isa指向 它自己
著名的 isa走位 & 繼承關(guān)系 圖
根據(jù)上面的探索以及各種驗(yàn)證,對(duì)象、類、元類、根元類的關(guān)系如下圖所示

isa走位
isa的走向有以下幾點(diǎn)說明:
-
實(shí)例對(duì)象(Instance of Subclass)的isa指向類(class) -
類對(duì)象(class)isa指向元類(Meta class) -
元類(Meta class)的isa指向根元類(Root meta class) -
根元類(Root meta class)的isa指向它自己本身,形成閉環(huán),這里的根元類就是NSObject的元類
superclass走位
superclass(即繼承關(guān)系)的走向也有以下幾點(diǎn)說明:
- 類之間的繼承關(guān)系:
-
類(subClass)繼承自父類(superClass) -
父類(superClass)繼承自根類(RootClass),此時(shí)的根類是指NSObject -
根類繼承自nil,所以根類即NSObject可以理解為萬物起源,即無中生有
-
- 元類也存在繼承,元類之間的繼承關(guān)系如下:
-
子類的元類(meta SubClass)繼承自父類的元類(meta SuperClass) -
父類的元類(meta SuperClass)繼承自根元類(Root meta Class) -
根元類(Root meta Class)繼承于根類(Root class),此時(shí)的根類是指NSObject
-
- 【注意】
實(shí)例對(duì)象之間沒有繼承關(guān)系,類之間有繼承關(guān)系
objc_class & objc_object
isa走位我們理清楚了,又來了一個(gè)新的問題:為什么 對(duì)象 和 類都有isa屬性呢?這里就不得不提到兩個(gè)結(jié)構(gòu)體類型:objc_class & objc_object
- 在objc4源碼中搜索
objc_class的定義,源碼中對(duì)其的定義有兩個(gè)版本-
舊版:位于runtime.h中,在OBJC2中已廢棄
image -
新版:位于objc-runtime-new.h中,我們后面的類的結(jié)構(gòu)分析也是基于新版來分析的。
image
-
從新版的定義中,可以看到 objc_class 結(jié)構(gòu)體類型是繼承自 objc_object的
- 在objc4源碼中搜索
objc_object的定義,發(fā)現(xiàn)也是有兩個(gè)版本-
一個(gè)是位于
objc.h中,從編譯的main.cpp中可以看出,使用的是這個(gè)版本的objc_object
image -
另外一個(gè)位于
objc-privat.h
image
-
以下是編譯后的main.cpp中的objc_object 和NSObject_IMPL的定義
struct objc_object {
Class _Nonnull isa __attribute__((deprecated));
};
struct NSObject_IMPL {
Class isa;
};
typedef struct objc_class *Class;
【問題】objc_class 與 objc_object 有什么關(guān)系?
通過上述的源碼查找以及main.cpp中底層編譯源碼,有以下幾點(diǎn)說明:
objc_class繼承自objc_object類型,其中objc_object也是一個(gè)結(jié)構(gòu)體,且有一個(gè)isa屬性,所以objc_class也擁有了isa屬性mian.cpp底層編譯文件中,
objc_object中的isa在底層是由Class定義的,其中Class的底層編碼是struct objc_class *的別名(即結(jié)構(gòu)體指針),所以NSObject也擁有了isa屬性NSObject是一個(gè)類,用它初始化一個(gè)實(shí)例對(duì)象objc,objc滿足objc_object的特性(即有isa屬性),主要是因?yàn)?code>isa 是由NSObject從objc_class繼承過來的,而objc_class繼承自objc_object,objc_object有isa屬性。所以對(duì)象都有一個(gè)isaobjc_object(結(jié)構(gòu)體)是當(dāng)前的根對(duì)象,所有的對(duì)象都有這樣一個(gè)特性objc_object,即擁有isa屬性
總結(jié)
- 所有的
對(duì)象、類、元類都有isa屬性 - 所有的
對(duì)象都是由objc_object繼承來的 - 簡(jiǎn)單概括就是
萬物皆對(duì)象,萬物皆來源于objc_object,有以下兩點(diǎn)結(jié)論:- 所有以
objc_object為模板,創(chuàng)建的對(duì)象,都有isa屬性 - 所有以
objc_class為模板,創(chuàng)建的類,都有isa屬性
- 所有以
類結(jié)構(gòu)分析
接下來主要是分析類信息中存儲(chǔ)了哪些內(nèi)容
內(nèi)存偏移
在分析類結(jié)構(gòu)之前,需要先了解內(nèi)存偏移,因?yàn)轭愋畔⒅性L問時(shí),需要使用內(nèi)存偏移
普通指針
// 普通指針
int a = 10; // 變量
int b = 11;
int c = 12;
int d = 13;
NSLog(@"%d -- %p", a, &a);
NSLog(@"%d -- %p", b, &b);
NSLog(@"%d -- %p", c, &c);
NSLog(@"%d -- %p", d, &d);
打印結(jié)果如下圖所示:

- 變量a、b、c、d存儲(chǔ)在
棧區(qū),是連續(xù)的內(nèi)存地址,棧區(qū)直接存儲(chǔ)的是值 - a、b、c、d的地址之間都相差
4字節(jié),這取決于他們存儲(chǔ)的類型,Int類型占4字節(jié) - 棧區(qū)的地址 比 堆區(qū)的地址 大
- 棧是從
高地址->低地址,向下延伸,由系統(tǒng)自動(dòng)管理,是一片連續(xù)的內(nèi)存空間 - 堆是從
低地址->高地址,向上延伸,由程序員管理,堆空間結(jié)構(gòu)類似于鏈表,是不連續(xù)的
對(duì)象指針
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 對(duì)象指針
HTPerson *p1 = [[HTPerson alloc] init];
HTPerson *p2 = [[HTPerson alloc] init];
NSLog(@"%@ -- %p", p1, &p1);
NSLog(@"%@ -- %p", p2, &p2);
NSLog(@"end");
}
return 0;
}
打印結(jié)果如下圖所示:

-
alloc開辟的內(nèi)存在堆區(qū),局部變量存儲(chǔ)在棧區(qū) - p1、p2存儲(chǔ)在棧區(qū),p1、p2內(nèi)存中存儲(chǔ)的是
[HTPerson alloc]的堆區(qū)地址
數(shù)組指針
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 數(shù)組指針
int c[4] = {1,2,3,4};
int *d = c;
NSLog(@"%p - %p - %p ", &c, &c[0], &c[1]);
NSLog(@"%p - %p - %p ", d, d+1, d+2);
for (int i = 0; i<4; i++) {
//(d+i) 取地址里面的值
int value = *(d+i);
NSLog(@"value = %d",value);
}
NSLog(@"end");
}
return 0;
}
打印結(jié)果如下圖所示:

-
&c和&c[0]都是取首地址,即數(shù)組名等于首地址 -
&c與&c[1]相差4字節(jié),地址之間相差的字節(jié)數(shù),主要取決于存儲(chǔ)的數(shù)據(jù)類型,即步長(zhǎng) - 可以通過
首地址+偏移量取出數(shù)組中的其他元素,其中偏移量等于步長(zhǎng) * 數(shù)組的下標(biāo)
類結(jié)構(gòu)組成
在前面的內(nèi)容,我們可以得到類的首地址,但是我們還不清楚類的結(jié)構(gòu)是什么,里面都存儲(chǔ)了什么東東
打開objc-818.2源碼,在objc-runtime-new.h中找到 objc_class的定義,如下
typedef struct objc_class *Class;
struct objc_class : objc_object {
...
// Class ISA; // 8字節(jié)
Class superclass; //8字節(jié)
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
// ...方法部分省略,未貼出
}
objc_class繼承自objc_object,因此擁有isa屬性,從源碼我們可以看出,objc_class包含下列四個(gè)變量:
-
isa屬性:結(jié)構(gòu)體指針,繼承自objc_object的isa,占8字節(jié) -
superclass屬性:結(jié)構(gòu)體指針,指向父類的地址,占8字節(jié) -
cache屬性:是一個(gè)cache_t結(jié)構(gòu)體類型,取決于cache_t內(nèi)部結(jié)構(gòu)體成員的大小,占16字節(jié) -
bits屬性:是一個(gè)class_data_bits_t結(jié)構(gòu)體類型,占8字節(jié)
我們可以通過lldb+sizeof()查看cache_t和class_data_bits_t的內(nèi)存大小,如下圖所示:

【驗(yàn)證】objc_class的內(nèi)存大小
定義兩個(gè)類HTPerson和HTTeacher,通過斷點(diǎn)來查看類和元類的地址,尋找規(guī)律

- 從上圖可以發(fā)現(xiàn),這幾個(gè)類地址之間相差
0x28(40字節(jié)),正好和objc_class結(jié)構(gòu)體中成員變量所占的內(nèi)存相同,都是40字節(jié)
探究類里面的各個(gè)成員變量,成員變量的內(nèi)存地址如下
-
isa的內(nèi)存地址是類首地址 -
superclass的內(nèi)存地址是首地址+0x8 -
cache的內(nèi)存地址是首地址+0x10 -
bits的內(nèi)存地址是首地址+0x20
isa屬性
objc_class繼承自objc_object,因此擁有isa屬性,isa是結(jié)構(gòu)體指針,占8字節(jié)
-
類對(duì)象(class)isa指向元類(Meta class) -
元類(Meta class)的isa指向根元類(Root meta class) -
根元類(Root meta class)的isa指向它自己本身,形成閉環(huán),這里的根元類就是NSObject的元類
superclass屬性
Class superclass; //8字節(jié)也是結(jié)構(gòu)體指針,占8字節(jié)
- 類之間的繼承關(guān)系:
-
類(subClass)繼承自父類(superClass) -
父類(superClass)繼承自根類(RootClass),此時(shí)的根類是指NSObject -
根類繼承自nil,所以根類即NSObject可以理解為萬物起源,即無中生有
-
- 元類也存在繼承,元類之間的繼承關(guān)系如下:
-
子類的元類(meta SubClass)繼承自父類的元類(meta SuperClass) -
父類的元類(meta SuperClass)繼承自根元類(Root meta Class) -
根元類(Root meta Class)繼承于根類(Root class),此時(shí)的根類是指NSObject
-
- 【注意】
實(shí)例對(duì)象之間沒有繼承關(guān)系,類之間有繼承關(guān)系
cache屬性
cache_t結(jié)構(gòu)體大小
首先,我們來探究cache_t結(jié)構(gòu)體,源碼定義如下
typedef unsigned long uintptr_t;
#if __LP64__
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
#else
typedef uint16_t mask_t;
#endif
struct cache_t {
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask; //8字節(jié)
union {
struct {
explicit_atomic<mask_t> _maybeMask; //4字節(jié)
#if __LP64__
uint16_t _flags; //2字節(jié)
#endif
uint16_t _occupied;//2字節(jié)
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache; // 8字節(jié)
};
//...
//下面是一些static屬性和方法,并不影響結(jié)構(gòu)體的內(nèi)存大小,主要是因?yàn)閟tatic類型的屬性 不存在結(jié)構(gòu)體的內(nèi)存中
}
cache_t是結(jié)構(gòu)體類型,有兩個(gè)成員變量:_bucketsAndMaybeMask和一個(gè)聯(lián)合體
-
_bucketsAndMaybeMask是uintptr_t類型,占8字節(jié) - 聯(lián)合體里面有兩個(gè)成員變量:
結(jié)構(gòu)體和_originalPreoptCache,聯(lián)合體由最大的成員變量的大小決定-
_originalPreoptCache是preopt_cache_t *結(jié)構(gòu)體指針,占8字節(jié) - 結(jié)構(gòu)體中有
_maybeMask、_flags、_occupied三個(gè)成員變量。-
_maybeMask的大小取決于mask_t即uint32_t,占4字節(jié) -
_flags是uint16_t類型,占2字節(jié) -
_occupied是uint16_t類型,占2字節(jié)
-
-
所以cache_t的大小等于 8+8或者8+4+2+2,即16字節(jié)
??我們看幾個(gè)重要的函數(shù)
fastInstanceSize函數(shù)
size_t fastInstanceSize(size_t extra) const {}記錄實(shí)例對(duì)象需要分配的內(nèi)存大小(16字節(jié)對(duì)齊),我們?cè)?a href="http://www.itdecent.cn/p/aa8c970191e0" target="_blank">iOS底層原理02:alloc & init & new 源碼分析已經(jīng)分析過了
size_t fastInstanceSize(size_t extra) const
{
ASSERT(hasFastInstanceSize(extra));
// Gcc的內(nèi)建函數(shù) __builtin_constant_p 用于判斷一個(gè)值是否為編譯時(shí)常數(shù),如果參數(shù)EXP 的值是常數(shù),函數(shù)返回 1,否則返回 0
if (__builtin_constant_p(extra) && extra == 0) {
return _flags & FAST_CACHE_ALLOC_MASK16;
} else {
size_t size = _flags & FAST_CACHE_ALLOC_MASK;
// remove the FAST_CACHE_ALLOC_DELTA16 that was added
// by setFastInstanceSize
// 刪除由setFastInstanceSize添加的FAST_CACHE_ALLOC_DELTA16 8個(gè)字節(jié)
return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
}
}
insert函數(shù)
void insert(SEL sel, IMP imp, id receiver);函數(shù)用來插入sel和imp,方法緩存,我們會(huì)在后面的篇章進(jìn)行分析
bits屬性
bits屬性是class_data_bits_t結(jié)構(gòu)體類型,有一個(gè)成員變量bits,占8字節(jié),結(jié)構(gòu)如下
struct class_data_bits_t {
friend objc_class; // 友元:objc_class可以調(diào)用 class_data_bits_t中的私有屬性和方法
// Values are the FAST_ flags above.
uintptr_t bits; // 8字節(jié),類似于isa,通過位域來存儲(chǔ)數(shù)據(jù)
// ...
public:
// 通過data()獲取數(shù)據(jù)
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
// ...方法較多
}
- 繼續(xù)查看
class_rw_t結(jié)構(gòu)體
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
explicit_atomic<uintptr_t> ro_or_rw_ext;
Class firstSubclass;
Class nextSiblingClass;
// ...
const class_ro_t *ro() const {...}
void set_ro(const class_ro_t *ro) {...}
const method_array_t methods() const {...}
const property_array_t properties() const {...}
const protocol_array_t protocols() const {...}
}

現(xiàn)在我們對(duì)objc_class的結(jié)構(gòu)有了初步的認(rèn)識(shí),在后面的篇章中繼續(xù)深入分析bits和cache兩個(gè)屬性
類結(jié)構(gòu)分析傳送門:



