前言
等風(fēng)來不如追風(fēng)去,總有那么一個人在這風(fēng)景正好的季節(jié)來到你的身邊雅潭。在上一篇探究了對象
的本質(zhì)和isa
指針的底層均蜜,那么我們繼續(xù)來看類
的底層結(jié)構(gòu)担平。
補(bǔ)充知識
- 在OC環(huán)境下使用的類示绊,在底層都有替換的類去實現(xiàn)
isa 和類的關(guān)聯(lián)
類的isa
探索對象的時候我們已經(jīng)知道,對象的結(jié)構(gòu)體中的isa
指向的是類
暂论,這個時候就會想面褐,類
也是一個對象,類
中也有isa
取胎,那么類
的isa
又指向哪里呢展哭?如下圖:
JCPerson
類對應(yīng)了兩個內(nèi)存地址0x00000001000085f0
和0x00000001000085c8
平匈,哪一個才是JCPerson
的地址呢?一個類
在內(nèi)存中存在幾個內(nèi)存地址
呢泉孩?接下來看下圖:
看到這里應(yīng)該非常清晰了大州,一個
類
只有一個內(nèi)存地址,0x00000001000085f0
是JCPerson
的類地址漆撞,而0x00000001000085c8
這個類地址蘋果把它叫做元類
總結(jié):
-
元類
由系統(tǒng)編譯器自動生成和編譯,與創(chuàng)建者無關(guān) -
對象
的isa
指向類
,類對象
的isa
指向元類
isa的走位圖
上面我們已經(jīng)分析了對象
映挂、類
的isa
的走向,那么元類
的isa
又指向哪里呢盗尸?結(jié)合LLDB來進(jìn)行探索柑船。
- 對象
isa
--> 類isa
--> 元類isa
--> 根元類isa
--> 根元類(自己) - 根類(
NSObject
)isa
--> 根元類isa
--> 根元類(自己)
isa
的流程圖:
類、元類泼各、根元類繼承圖
創(chuàng)建JCPerson
,JCTeacher
,NSObject
的相關(guān)代碼鞍时,探究一下它們的繼承
關(guān)系。
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"%@",class_getSuperclass(JCTeacher.class));
NSLog(@"%@",class_getSuperclass(JCPerson.class));
NSLog(@"%@",class_getSuperclass(NSObject.class));
# NSObject實例對象
NSObject *object1 = [NSObject alloc];
# NSObject類
Class class = object_getClass(object1);
# NSObject元類
Class metaClass = object_getClass(class);
# NSObject根元類
Class rootMetaClass = object_getClass(metaClass);
# NSObject根根元類
Class rootRootMetaClass = object_getClass(rootMetaClass);
NSLog(@"\nNSObject實例對象 %p\nNSObject類 %p\nNSObject元類 %p\nNSObject根元類 %p\nNSObject根根元類 %p",object1,class,metaClass,rootMetaClass,rootRootMetaClass);
# NSObject 根類特殊情況
Class nsuperClass = class_getSuperclass(NSObject.class);
NSLog(@"%@ - %p",nsuperClass,nsuperClass);
# 根元類 -> NSObject
Class rnsuperClass = class_getSuperclass(metaClass);
NSLog(@"%@ - %p",rnsuperClass,rnsuperClass);
# JCPerson元類和元類的父類
Class pMetaClass = object_getClass(JCPerson.class);
Class psuperClass = class_getSuperclass(pMetaClass);
NSLog(@"%@ - %p",psuperClass,psuperClass);
# JCTeacher元類和元類的父類
Class tMetaClass = object_getClass(JCTeacher.class);
Class tsuperClass = class_getSuperclass(tMetaClass);
NSLog(@"%@ - %p",tsuperClass,tsuperClass);
}
return 0;
}
源碼分析中知道逆巍,
NSObject
的父類打印結(jié)果是nil
;NSObject
根元類的父類的地址等于NSObject
類的地址莽使;JCPerson
元類的父類的地址等于NSObject
的元類锐极。
-
JCTeacher
-->JCPerson
-->NSObject
-->nil
-
JCTeacher元類
-->JCPerson元類
-->NSObject元類
-->NSObject根元類
-->NSObject
類
的繼承圖:
isa
流程圖和繼承鏈:
內(nèi)存偏移
在前面探究對象
的底層實現(xiàn),我們了解到對象屬性
的getter
方法底層實現(xiàn)是通過首地址
+內(nèi)存偏移
的方式去獲取內(nèi)存中的變量芳肌。接下來我們來看一下內(nèi)存偏移
灵再。
基本指針
# 普通指針
int a = 10; //
int b = 10; //
JCNSLog(@"%d -- %p",a,&a);
JCNSLog(@"%d -- %p",b,&b);
==========: 10 -- 0x7ffeefbff4ec
==========: 10 -- 0x7ffeefbff4e8
-
a
的地址是0x7ffeefbff4ec
,b
的地址是0x7ffeefbff4e8
亿笤,相差4個字節(jié)翎迁,int
類型是4個字節(jié)的長度 -
a
>b
的地址,從高地址
往低地址
偏移净薛,這符合棧內(nèi)存
的分配原則
對象指針
# 對象
JCPerson *p1 = [JCPerson alloc];
JCPerson *p2 = [JCPerson alloc];
JCNSLog(@"%@ -- %p",p1,&p1);
JCNSLog(@"%@ -- %p",p2,&p2);
==========: <JCPerson: 0x1004075a0> -- 0x7ffeefbff4e8
==========: <JCPerson: 0x100408570> -- 0x7ffeefbff4e0
-
alloc
開辟的內(nèi)存在堆區(qū)
汪榔,指針地址
在棧區(qū)
-
堆區(qū)
是從低
地址 -->高
地址,棧區(qū)
是從高
地址 -->低
地址
數(shù)組指針
int c[4] = {1,2,3,4};
int *d = c;
JCNSLog(@"%p - %p - %p",&c,&c[0],&c[1]);
JCNSLog(@"%p - %p - %p",d,d+1,d+2);
for (int i = 0; i<4; i++) {
int value = *(d+i);
JCNSLog(@"----%d",value);
}
==========: 0x7ffeefbff4e0 - 0x7ffeefbff4e0 - 0x7ffeefbff4e4
==========: 0x7ffeefbff4e0 - 0x7ffeefbff4e4 - 0x7ffeefbff4e8
==========: ----1
==========: ----2
==========: ----3
==========: ----4
- 數(shù)組的地址就是元素的
首地址
罕拂,即&c == &c[0]
- 數(shù)組中每個元素的地址可以通過:
首地址 + n*元素類型大小
來獲取揍异,只需要數(shù)組中元素數(shù)據(jù)類型
相同 - 數(shù)組中的每個元素的
地址間隔
是通過當(dāng)前元素的數(shù)據(jù)類型
決定的
總結(jié):
- 內(nèi)存偏移可以根據(jù)
首地址
+偏移值
方式來獲取各個數(shù)據(jù)的內(nèi)存地址
類結(jié)構(gòu)的分析
上面我們已經(jīng)了解了內(nèi)存偏移
的知識,接下來我們來探究類
的底層結(jié)構(gòu)爆班。
對象
底層結(jié)構(gòu)中存放在屬性
衷掷、成員變量
等數(shù)據(jù);從圖中打印可以看出類
是有內(nèi)存的柿菩,那么它里面存放著什么呢戚嗅?接下來分析類
底層的數(shù)據(jù)結(jié)構(gòu)。
類的底層結(jié)構(gòu):
探索對象isa
的過程中,我們已經(jīng)知道isa
在底層是Class
類型懦胞,Class
類型是objc_class *
替久,所有類
的底層實現(xiàn)都是objc_class
,通過全局搜索可以找到如下代碼:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE; # OBJC2不可用
通過上面這段代碼我們發(fā)現(xiàn)在OBJC2
中不可用躏尉,而現(xiàn)在的版本基本是在用OBJC2
蚯根,所以這不是我們分析的,接下來在看一下如下代碼:
struct objc_class : objc_object {
objc_class(const objc_class&) = delete;
objc_class(objc_class&&) = delete;
void operator=(const objc_class&) = delete;
void operator=(objc_class&&) = delete;
// 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
...
下面全部是方法胀糜,不需要分析颅拦,省略
}
源碼分析:objc_class
是繼承objc_object
,這說明類
也是對象
教藻,所謂萬物皆對象距帅。objc_class
里面有一個隱藏成員變量isa
,我們前面已經(jīng)分析過了括堤,下面還有三個成員變量superclass
,cache
,bits
碌秸,我們知道首地址就是isa
,那么我們可以通過首地址
+偏移量
的方式去獲取成員變量的地址悄窃,然后獲取值讥电。
-
isa
是結(jié)構(gòu)體指針,占8
字節(jié) -
Class superclass
是Class
類型广匙,也是屬于結(jié)構(gòu)體指針允趟,占8
字節(jié) -
cache
是cache_t
結(jié)構(gòu)體,結(jié)構(gòu)體大小由內(nèi)部的變量決定 -
bits
是class_data_bits_t
結(jié)構(gòu)體鸦致,如果知道前面三個成員變量的大小潮剪,那么就可以得到bits
的地址
前面三個成員變量已經(jīng)知道了前兩個的大小,只要知道cache
的內(nèi)存大小分唾,接下來看一下cache_t
的內(nèi)存大小
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
union {
struct {
explicit_atomic<mask_t> _maybeMask; //4
#if __LP64__
uint16_t _flags; //2
#endif
uint16_t _occupied; //2
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache; //8
};
...
下面的一些方法直接省略(static類型的是在內(nèi)存的全局區(qū)抗碰,不在結(jié)構(gòu)體里面的內(nèi)存)
}
cache_t
是一個結(jié)構(gòu)體類型,內(nèi)部包含了_bucketsAndMaybeMask
和一個聯(lián)合體
.
-
_bucketsAndMaybeMask
是uintptr_t
類型绽乔,而uintptr_t
是無符號長整型占8個字節(jié) -
聯(lián)合體
內(nèi)存大小由成員變量中的最大變量的內(nèi)存大小決定弧蝇,該聯(lián)合體由一個結(jié)構(gòu)體
和_originalPreoptCache
兩個成員變量組成,由于聯(lián)合體存在互斥的折砸,所以只需要得到其中最大變量的內(nèi)存大小 -
_originalPreoptCache
是preopt_cache_t *
結(jié)構(gòu)體指針類型看疗,占8個字節(jié) - 結(jié)構(gòu)體中有
_maybeMask
,_flags
,_occupied
。_maybeMask
是mask_t
類型睦授,mask_t
又是uint32_t
類型两芳,占4個字節(jié),_flags
和_occupied
是uint16_t
類型去枷,占2個字節(jié)
綜上所述cache_t
的內(nèi)存大小為16
字節(jié)怖辆。
總結(jié):
-
isa
內(nèi)存地址為首地址
-
superclass
地址為首地址
+0x08
-
cache_t
地址為首地址
+0x10
-
bits
地址為首地址
+0x20
類的結(jié)構(gòu)圖.png
bits數(shù)據(jù)結(jié)構(gòu)
上面已經(jīng)了解類
的基本結(jié)構(gòu)是复,isa
和superclass
已經(jīng)探究過了,接下來我們先來研究一下成員變量bits
存儲了哪些信息竖螃?
struct objc_class : objc_object {
objc_class(const objc_class&) = delete;
objc_class(objc_class&&) = delete;
void operator=(const objc_class&) = delete;
void operator=(objc_class&&) = delete;
// 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 getSuperclass() const {...}
void setSuperclass(Class newSuperclass) {...}
class_rw_t *data() const {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
bits
是class_data_bits_t
類型淑廊,底層源碼中還有一個data()
,返回bits.data()
特咆,這有可能就是bits
中存儲的數(shù)據(jù)季惩。data()
的類型是class_rw_t
。
struct class_rw_t {
... //省略一些沒用的方法
const class_ro_t *ro() const {
auto v = get_ro_or_rwe();
if (slowpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
}
return v.get<const class_ro_t *>(&ro_or_rw_ext);
}
void set_ro(const class_ro_t *ro) {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro = ro;
} else {
set_ro_or_rwe(ro);
}
}
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 *>(&ro_or_rw_ext)->methods;
} else {
return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->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 *>(&ro_or_rw_ext)->properties;
} else {
return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->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 *>(&ro_or_rw_ext)->protocols;
} else {
return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
}
}
class_rw_t
是一個結(jié)構(gòu)體腻格,里面存儲著方法
,屬性
,協(xié)議
列表蜀备,接下來驗證是否存儲在class_rw_t
中。
屬性探究( properties() )
@interface JCPerson : NSObject{
NSString *subject;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *hobby;
- (void)sayNB;
+ (void)say666;
@end
-
property_list_t
中存儲name
,hobby
屬性 -
p $7.get(2)
會提示數(shù)組越界荒叶,沒有找到成員變量subject
問題:那么定義的subject
成員變量存到哪里去了呢?
補(bǔ)充:成員變量
class_rw_t
中除了有屬性
,方法
,協(xié)議
以外输虱,還有class_ro_t
結(jié)構(gòu)體指針類型的ro()
些楣,在class_ro_t
結(jié)構(gòu)體中我們可以找到ivar_list_t
類型的指針ivars
,成員變量
會不會存在這里呢宪睹?
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
union {
const uint8_t * ivarLayout;
Class nonMetaclass;
};
explicit_atomic<const char *> name;
// With ptrauth, this is signed if it points to a small list, but
// may be unsigned if it points to a big list.
void *baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
源碼和LLDB分析:
- 成員變量底層實現(xiàn)是
ivar_t
愁茁,存儲在class_ro_t
成員變量列表 - 系統(tǒng)是自動給
屬性
添加_屬性名
的變量,存儲在class_ro_t
成員變量列表
方法探究( methods() )
通過
LLDB
的方式亭病,可以得到定義的方法
鹅很。但是發(fā)現(xiàn)使用get(index)
的方式無法得到,?使用get(index).big()
才能獲取罪帖,這是為什么呢促煮?
struct property_t {
const char *name;
const char *attributes;
};
struct method_t {
struct big {
SEL name;
const char *types;
MethodListIMP imp;
};
big &big() const {
ASSERT(!isSmall());
return *(struct big *)this;
}
SEL name() const {
if (isSmall()) {
return (small().inSharedCache()
? (SEL)small().name.get()
: *(SEL *)small().name.get());
} else {
return big().name;
}
}
const char *types() const {
return isSmall() ? small().types.get() : big().types;
}
IMP imp(bool needsLock) const {
if (isSmall()) {
IMP imp = remappedImp(needsLock);
if (!imp)
imp = ptrauth_sign_unauthenticated(small().imp.get(),
ptrauth_key_function_pointer, 0);
return imp;
}
return big().imp;
}
源碼分析:-
屬性
底層實現(xiàn)是property_t
,在property_t
結(jié)構(gòu)體中定義了name
等變量 -
方法
底層實現(xiàn)是method_t
整袁,在method_t
結(jié)構(gòu)體中定義了一個big()
菠齿,通過big()
獲取SEL
和IMP
接下來我們繼續(xù)打印methods
,如下圖坐昙。
-
method_list_t
中有對象方法
,屬性的setter方法
和getter方法
-
method_list_t
中沒有獲取到類方法
問題:那么類方法
存儲到哪里去了呢绳匀?
補(bǔ)充:類方法
對象方法
存儲在類
中,那類方法
可能存儲在元類
炸客。
-
object_getClass
獲取到JCPerson
的元類 - 元類中
method_list_t
中存儲著類方法
總結(jié):
- 類的結(jié)構(gòu)主要由
isa
,superclass
,cache
,bits
組成 -
bits
中存儲著屬性
,方法
,協(xié)議
-
屬性
存儲在property_list_t
中疾棵,而成員變量
存儲在class_ro_t
-->ivar_list_t
,系統(tǒng)為屬性
自動生成的_屬性名
的變量也存儲在class_ro_t
-->ivar_list_t
-
方法
存儲在method_list_t
中痹仙,method_list_t
主要存儲著對象方法
,屬性的setter方法
和getter方法
是尔,而類方法
存儲在元類
中的method_list_t