在分析類的結(jié)構(gòu)之前义桂,我們需要先搞清楚類的內(nèi)存分布情況耿戚。
類的內(nèi)存分布
首先創(chuàng)建一個(gè)Person類:
// Person 類
@interface Person : NSObject
- (void)sayHello;
+ (void)sayHi;
@end
@implementation Person
- (void)sayHello {
NSLog(@"Say Hello");
}
+ (void)sayHi {
NSLog(@"Say Hi");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
Person *person = [[Person alloc] init];
NSLog(@"%@",person);
}
return 0;
}
通過上圖中可以看出 0x00000001000012e8
和 0x00000001000012c0
為什么打印出的結(jié)果都是 Person
呢粟誓?這個(gè)東西其實(shí)就是元類
。
那么什么是元類呢?
元類
每一個(gè)對象都對應(yīng)一個(gè)類。
Person
類就是person
對象的類澳盐,換句話說就是person
對象的isa指針指向Person
對應(yīng)的結(jié)構(gòu)體;[Person class]
也是對象令宿,描述它的類就是元類叼耙,換句話說[Person class]
對象的isa指向的就是元類
元類的定義和創(chuàng)建都是由編譯器自動(dòng)完成。
元類保存了類方法的列表粒没。當(dāng)一個(gè)類方法被調(diào)用時(shí)筛婉,元類會(huì)首先查找它本身是否有該類的方法的實(shí)現(xiàn),如果沒有則該類會(huì)向它的父類查找該方法癞松,知道一直找到繼承鏈的頭爽撒。
從上圖中我們可以看到,我們通過isa可以一直找到NSObject
類响蓉。通過 person
對象的isa指針可以找到Person
類硕勿,通過Person
的isa指針可以找到元類Person
,而通過元類Person
的isa指針又可以找元類Person
的元類NSObject
枫甲,通過元類NSObject
的isa指針找到的還是NSObject
源武,其實(shí)這個(gè)時(shí)候的NSObject
叫做根元類
但是NSObject
的類和 NSObject.class
的指針地址不一樣呢?是NSObject類會(huì)在內(nèi)存中存在多份嗎想幻?
我們通過如下代碼打印來看下:
void getClassInfo() {
Class class1 = [Person class];
Class class2 = [Person alloc].class;
Class class3 = object_getClass([Person alloc]);
NSLog(@"\n%p\n%p\n%p\n",class1,class2,class3);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
getClassInfo();
}
return 0;
}
輸出結(jié)果如下:
2020-09-15 20:49:21.406345+0800 ObjectAnalysis[34085:811450]
0x1000012f0
0x1000012f0
0x1000012f0
Program ended with exit code: 0
可以看出其實(shí)類對象在整個(gè)內(nèi)存中只有一份粱栖。
因此我們可以得出一個(gè)著名的isa的走位圖。
isa的走位圖&繼承圖
isa查找流程
-
實(shí)例對象
的isa指針類對象
-
類對象
的isa指向元類對象
-
元類對象
的isa指向根元類
-
根元類
的isa指向它自己本身
脏毯,從而形成了閉環(huán)
繼承關(guān)系
類對象的繼承關(guān)系
-
類
繼承于它的父類
-
父類
繼承它的父類
... - 直到找到
根類
闹究,也就是NSObject
-
NSObject
則繼承于nil
,這也就是所有的根源抄沮,即無中生有
元類的繼承關(guān)系
-
子類的元類
繼承與父類的元類
-
父類的元類
繼承它的父類的元類
... - 直到找到
根元類
- 而
根元類
則是繼承于NSObject
tips:實(shí)例對象
是沒有繼承關(guān)系的跋核,只有類
才有繼承關(guān)系
對象的本質(zhì)
對象的本質(zhì)其實(shí)就是結(jié)構(gòu)體岖瑰,而編譯到底層都是繼承自objc_class
的結(jié)構(gòu)體
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() const {
return bits.data();
}
// 代碼過多,自動(dòng)省略
...
};
// objc_object
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
Class rawISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
// 代碼過多砂代,自動(dòng)省略
...
};
通過源碼可以看出 objc_class
其實(shí)就是繼承自 objc_object
蹋订。
所以我們可以看出所有的 對象
、類
和元類
都有isa指針刻伊,是因?yàn)槎祭^承自 objc_object
露戒,而 objc_object
中有個(gè)私有變量isa
。
objc_object捶箱、objc_class智什、isa、object丁屎、NSObject
的關(guān)系如下圖所示:
我們接下來重點(diǎn)就是分析類的結(jié)構(gòu)了
類的結(jié)構(gòu)分析
再開始前荠锭,先分析下objc_class
的結(jié)構(gòu)圖
- isa 是指向元類的指針
- superclass 是指向當(dāng)前類的父類
- cache 是用于緩存方法的,用于加速方法的調(diào)用
- bits 是存儲(chǔ)類的方法晨川、屬性证九、協(xié)議等信息的地方
一般我們獲取到bits
,然后才能獲取到我們需要的信息共虑,那么我們就需要進(jìn)行32位指針的偏移愧怜,為什么是32位指針的偏移,請看下面的代碼段分析:
struct objc_class : objc_object {
// Class ISA; // 指針占 8 字節(jié)
Class superclass; // 指針占 8 字節(jié)
cache_t cache; // 占 16 字節(jié)妈拌,具體分析拥坛,看cache_t 結(jié)構(gòu)體的分析
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() const {
return bits.data();
}
// 代碼過多,自動(dòng)省略
...
};
// mask_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
// 結(jié)構(gòu)體指針字節(jié)大小尘分,看里面的成員變量猜惋,而大部分都是 static 和方法都不存在結(jié)構(gòu)體里面
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets; // 結(jié)構(gòu)體指針 8 字節(jié)
explicit_atomic<mask_t> _mask; // uint32_t 占 4 字節(jié)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
#if __LP64__
uint16_t _flags; // 占 2 字節(jié)
#endif
uint16_t _occupied; // 占 2 字節(jié)
// 方法代碼過多,自動(dòng)省略
...
};
class_data_bits_t
在objc_class
結(jié)構(gòu)體中的注釋中已經(jīng)寫到class_data_bits_t
相當(dāng)于class_rw_t
指針加上rr/alloc
的標(biāo)志音诫。
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
而在class_data_bits_t
結(jié)構(gòu)體中為我們提供了一個(gè)用于返回其中的class_rw_t *
的方法
struct class_data_bits_t {
friend objc_class;
// Values are the FAST_ flags above.
uintptr_t bits;
// 代碼過多惨奕,自動(dòng)省略
...
public:
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
// 代碼過多,自動(dòng)省略
...
};
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
explicit_atomic<uintptr_t> ro_or_rw_ext;
Class firstSubclass;
Class nextSiblingClass;
// 省略過多的代碼
...
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()};
}
}
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};
}
}
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};
}
}
};
可以看出竭钝,我們可以通過class_rw_t
結(jié)構(gòu)體中提供的methods()
、properties()
雹洗、protocols()
獲取到對應(yīng)的方法香罐、屬性和協(xié)議。
那么接下來我們通過lldb
調(diào)試來查找對應(yīng)的class_data_bist_t bits
时肿,查看相應(yīng)的信息庇茫。
當(dāng)我們獲取到class_rw_t
結(jié)構(gòu)體信息后,接下來想要獲取到方法螃成、屬性或者協(xié)議旦签,就需要通過調(diào)用其提供的方法來進(jìn)行獲取查坪。
屬性列表 properties()
- 1、通過
properties()
可以獲取到對應(yīng)的屬性列表 - 2宁炫、通過get方法訪問第一個(gè)屬性偿曙,可以發(fā)現(xiàn)是我們定義的
name
屬性
【error】但是嘗試去獲取第二個(gè)屬性的時(shí)候,發(fā)現(xiàn)數(shù)組越界了羔巢,這是為什么呢望忆?我們不是還定義了一個(gè)hobby
嗎?
由此引出成員變量
竿秆、實(shí)例變量
和屬性
的區(qū)別
成員變量/實(shí)例變量/屬性
成員變量: Member Variable declarations
類: Class (description/template for an object)
實(shí)例: Instance (manifestation of a class)
消息: Message (sent to object to make it act)
方法: Method (code invoked by a Message)
實(shí)例變量: Instance Variable (object-specific storage)
超類/子類: Superclass/Subclass (Inheritance)
協(xié)議: Protocol (non-class-specific methods)
從上面的解釋启摄,可以看出,實(shí)例
是針對類
而言的幽钢,歉备。實(shí)例是指類的聲明,由此推理匪燕,實(shí)例變量是指由類聲明的對象蕾羊。
而屬性和成員變量的區(qū)別在于有無setter、getter方法谎懦。
方法列表 methods()
我們還是先通過lldb
調(diào)試來查找方法列表肚豺。
通過上圖的lldb調(diào)試過程,我們可以發(fā)現(xiàn):
-
method_list_t
中有方法的總個(gè)數(shù)count=4
- 會(huì)優(yōu)先存儲(chǔ)我們定義的實(shí)例方法
- 會(huì)有一個(gè)隱藏的析構(gòu)函數(shù)
.cxx_destruct
界拦,實(shí)現(xiàn)ARC下自動(dòng)釋放內(nèi)存的工作 -
setter
吸申、getter
方法的存儲(chǔ)
其實(shí)探索到這里,發(fā)現(xiàn)我們還遺留了兩個(gè)問題:
1享甸、成員變量的存儲(chǔ)
2截碴、類方法的存儲(chǔ)
成員變量的存儲(chǔ)
其實(shí)通過objc_class
的源碼,我們可以知道里面其實(shí)還有個(gè)ro
方法蛉威,會(huì)返回一個(gè)class_ro_t
的結(jié)構(gòu)體的信息日丹。打印信息如下:
在class_ro_t
結(jié)構(gòu)體中其實(shí)有很多的成員變量和方法,其實(shí)class_rw_t
中的ro
蚯嫌,在類的編譯期就已經(jīng)確定了屬性哲虾、方法以及要遵循的協(xié)議,objc_class
中的bits
中的data部分存放了ro
信息择示。而在runtime
運(yùn)行之后束凑,準(zhǔn)確的說是在運(yùn)行runtime
的realizeClass
方法時(shí),會(huì)生成class_rw_t
結(jié)構(gòu)體栅盲,該結(jié)構(gòu)體包含了class_ro_t
汪诉,并且更新了data部分。換成了class_rw_t
結(jié)構(gòu)體的地址谈秫。
由上圖的llbd
打印出的ivars
信息中扒寄,可以看到鱼鼓,我們生命的屬性,也會(huì)在ivars
中生成一個(gè) _name
的成員變量该编。這也就是我們?yōu)槭裁纯梢酝ㄟ^ _name
和self.name
來訪問屬性的原因了迄本。
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
// 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;
}
}
};
類方法的存儲(chǔ)
其實(shí)我們從上面的isa
走位圖中就可以知道一點(diǎn),所有的實(shí)例對象的isa
都是指向其元類
的上渴,那么我們是否可以查看其元類
中的方法列表岸梨。下面我們通過lldb
調(diào)試來看一下。
所以可以看出對象中的類方法
都是存儲(chǔ)在其元類
中的稠氮。