建議先看下
IOS底層(三): alloc相關(guān)1.初探 alloc, init, new源碼分析
IOS底層(八): alloc相關(guān): isa與類關(guān)聯(lián)源碼分析
首先看個例子:
XXX & 0x00007ffffffffff8ULL
這塊先解釋下, 之前我們講過ISA源碼
# define ISA_MASK 0x00007ffffffffff8ULL // x86_64下
inline Class
objc_object::ISA()
{
ASSERT(!isTaggedPointer());
#if SUPPORT_INDEXED_ISA
if (isa.nonpointer) {
uintptr_t slot = isa.indexcls;
return classForIndex((unsigned)slot);
}
return (Class)isa.bits;
#else
return (Class)(isa.bits & ISA_MASK);
#endif
}
這里 isa.bits & ISA_MASK
是返回類信息
我們分解看下上面例子
x/4gx test
讀一下內(nèi)存段, 第一個是isa
, 我們p/x一下isa & ISA_MASK
, 可看到有0x0000000100008388
, 我們接下來po
一下,po 0x0000000100008388
沒問題返回自定義類SATest
(① x/4gx 讀內(nèi)存段, 打印內(nèi)存情況, 第一個是isa指針地址
② p/xisa & ISA_MASK
是獲取類信息
, 返回的是類的指針地址
, 此時類是SATest
③ po 是打印類信息 (都是lldb
調(diào)試命令))既然是類信息, 我們再做一次
x/4gx
→p/x isa & ISA_MASK
→po
可看到po 0x0000000100008360(類isa指針地址)
也為SATest
既然是類信息, 我們再再做一次
x/4gx
→p/x isa & ISA_MASK
→po
可看到0x000000010036a140(類isa指針地址)
為NSObject
其實第二次時候就應(yīng)該有疑問, 為什么還是SATest
, 而第三次是NSObject
第二次中 0x0000000100008360
是之前isa
中獲取的isa
的指針地址, 即SATest類的類
, 在Apple中我們稱SATest類的類
為元類
, 之后的NSObject
也稱為元類
, 不過由于它源自根類
, 所以也可以成為根元類
(下面圖片是新走一遍的結(jié)果, 之前不小心給關(guān)了, 打印地址可能有點(diǎn)差別, 原理不變)
元類
首先了解一點(diǎn), 對象
isa
指向的類
, 類也是一個對象( 所以有個流傳, 萬物皆對象:) ), 這個對象我們一般稱為類對象
, 其isa
位域指向蘋果定義的元類
元類
是系統(tǒng)
給的, 其定義
和創(chuàng)建
都是由編譯器完成
,類
的歸屬
來自于元類
3.元類
是類對象
的類
, 每個類
都有一個獨(dú)一無二的元類
用來存儲類方法相關(guān)信息
4.元類
本身是沒有名稱的, 由于與類
相關(guān)聯(lián)
, 所以使用了同類名一樣的名稱
由上面例子也可以得到關(guān)系
對象
→ 類
→ 元類
→ NSObject
, NSObject
元類指向自身
總結(jié)
-
對象
的isa
是類
(也稱類對象
) -
類
的isa
指向元類
-
元類
的isa
指向NSObject
(根元類
) -
NSObject
的isa指向本身
擴(kuò)展個問題, 剛才看到自定義的一個類, 有元類有根元類依次循環(huán), 那么豈不是, 創(chuàng)建一個類, 系統(tǒng)會自動幫我們創(chuàng)建多個NSObject?
我們可以這樣驗證下
Class cls1 = [SATest class];
Class cls2 = [SATest alloc].class;
Class cls3 = object_getClass([SATest alloc]);
NSLog(@"cls1: %p", cls1);
NSLog(@"cls2: %p", cls2);
NSLog(@"cls3: %p", cls3);
可看到打印地址只有一個, 所以NSObject只有一份, 或者說NSObject(根元類)
在內(nèi)存中只有一份。(類的信息在內(nèi)存中永遠(yuǎn)只存在一份, 類對象只有一份
)
或者通過lldb驗證也可以, 看下下面圖片即可(根元類只是指向自己)
isa走勢關(guān)系圖
(留意下虛線是isa, 實線是superclass)
-
isa走勢:
實例對象(Instance)
isa指向 → Class(類)
isa 指向 → meta(元類)
isa 指向 → root (meta 根元類)
, 而root (meta 根元類)
isa 指向 → NSObject
父類實例對象也是一樣走勢
如果是類NSObject, 會省去一個元類指向
-
superclass走勢:
子類(SubClass)
繼承 → 父類(SuperClass) →
繼承 → 根類(Root Class)
根類即NSObject
無繼承
子元類(meta SubClass)
繼承 → 父元類(meta SuperClass) →
繼承 → 根元類(meta Root Class)
繼承 → NSObject
- 實例對象沒有繼承關(guān)系, 類才會有繼承關(guān)系, NSObject繼承nil
objc_class & objc_object
既然提到了類和對象, 我們看下objc_class
和objc_object
源碼
1. typedef struct objc_class *Class;
2. 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;
/* Use `Class` instead of `struct objc_class *` */
// objc-runtime-new.h
3. 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 {
#if __has_feature(ptrauth_calls)
// objc.h
1. struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
// objc-private.h
2. struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA(bool authenticated = false);
// 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();
uintptr_t isaBits() const;
// initIsa() should be used to init the isa of new objects only.
// If this object already has an isa, use changeIsa() for correctness.
// initInstanceIsa(): objects with no custom RR/AWZ
// initClassIsa(): class objects
// initProtocolIsa(): protocol objects
// initIsa(): other objects
void initIsa(Class cls /*nonpointer=false*/);
void initClassIsa(Class cls /*nonpointer=maybe*/);
void initProtocolIsa(Class cls /*nonpointer=maybe*/);
void initInstanceIsa(Class cls, bool hasCxxDtor);
// changeIsa() should be used to change the isa of existing objects.
// If this is a new object, use initIsa() for performance.
Class changeIsa(Class newCls);
...
首先可以看到objc_class
和objc_object
都有一個 isa
指針, 這個isa來自于objc_object
(struct objc_object { Class _Nonnull isa OBJC_ISA_AVAILABILITY; };
), 且在新版objc-runtime-new.h
里面可看到objc_class
是繼承于objc_object
的
總結(jié):
Class
底層是objc_class
(typedef struct objc_class *Class;
結(jié)構(gòu)體類型 ), 所有類都是以objc_class
為模板繼承過來的。objc_object
與對象的關(guān)系是繼承關(guān)系
, 所有的對象都是以objc_object
為模板繼承過來的, (但是真正到底層的是一個objc_object(C/C++)結(jié)構(gòu)體類型)結(jié)構(gòu)體類型
objc_class
繼承自objc_object
, 其中objc_object
(結(jié)構(gòu)體), 因為是繼承關(guān)系且有一個isa
屬性, 所以objc_class
也擁有了isa
屬性 ( 其實的對象
,類
,元類
都有isa屬性)NSObject
中的isa
在底層是由Class 定義的, 其中class
的底層編碼來自objc_class
類型, 所以NSObject也擁有了isa屬性NSObject
是一個類,用它初始化一個實例對象objc茎刚,objc 滿足objc_object
的特性(即有isa屬性),主要是因為isa 是由 NSObject 從objc_class繼承過來的秒咐,而objc_class繼承自objc_object股囊,objc_object 有isa屬性潮饱。所以對象都有一個isa
,isa
表示指向豺型,來自于當(dāng)前的objc_object
(對象, 類, 元類都有isa
)objc_object
是根對象
仲智,所有的對象都有這樣一個特性 objc_object,即擁有isa屬性在結(jié)構(gòu)層面可以通俗的理解為上層OC 與 底層的對接:
① 下層是通過結(jié)構(gòu)體
定義的 模板姻氨,例如 objc_class、objc_object
② 上層是通過底層的模板創(chuàng)建的 一些類型剪验,例如CATest
類結(jié)構(gòu)分析
首先還是先看一個例子
例子1: 普通指針
定義2個變量a, b = 10, 打印兩個變量值以及內(nèi)存地址
a 和 b 為變量都指向10, 10是系統(tǒng)開辟的固定內(nèi)存空間, 其他需要10的值的變量都可以指向內(nèi)存固定生成的10
a 和 b 地址不一樣, 這是一種拷貝, 屬于
值拷貝
, 也成深拷貝
, 可發(fā)現(xiàn)a, b地址相差 4 個字節(jié),這取決于a啸臀、b的類型
例子2: 對象指針
&p1/&p2 是
二級指針
, 指向?qū)ο蟮闹羔樀刂?0x7ffeefbff478, 0x7ffeefbff480 為對象指針
)p1/p2 是
一級指針
, 指向的 [SATest alloc] 開辟空間的內(nèi)存地址SATest
為[SATest alloc]
創(chuàng)建內(nèi)存空間, [SATest alloc]開辟空間的isa
指向SATest
例子3: 數(shù)組指針
&arr == &arr[0] == 首地址, 其實他都是取的首地址, 數(shù)組地址其實就是數(shù)組第一個元素地址即
數(shù)組名為首地址
&arr[0]與%arr[1]相差
4字節(jié)
, 取決于數(shù)據(jù)類型
數(shù)組類型指針可以通過
首地址+偏移量
得到其他元素(偏移量為數(shù)組下標(biāo)
)移動的字節(jié)數(shù) 等于 偏移量 * 數(shù)據(jù)類型字節(jié)數(shù)
, 這個根據(jù)&arr[0], &arr[1]看出, 兩者相差4
有了上面的概念, 便于我們理解之后的探索
bits探索
再回頭看下objc_class
源碼(在 objc-runtime-new.h
)
objc-runtime-new.h
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_rw_t *data() const {
return bits.data();
}
...
}
拋去那些刪除的, ISA
占8字節(jié)
, superclass
為Class
類型占8字節(jié)
(這里的Class
是由objc_object
定義的届宠,是一個指針), bits
只有首地址經(jīng)過上面3個屬性的內(nèi)存大小總和的平移,才能獲取到bits
(bits
有所以我們想要的信息), 那么我們接下來看下cache
struct cache_t {
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
union {
struct {
explicit_atomic<mask_t> _maybeMask;
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache;
};
...
}
先介紹幾個定義
-
uintptr_t
定義typedef unsigned long uintptr_t;
,long
8字節(jié) -
mask_t
定義typedef uint32_t mask_t;
,typedef unsigned int uint32_t;
, int類型占4字節(jié) -
uint32_t
定義typedef unsigned int uint32_t;
, int類型占4字節(jié) -
uint16_t
定義typedef unsigned short uint16_t;
short類型占2字節(jié)
拆開看一下
占8字節(jié)
explicit_atomic<uintptr_t> _bucketsAndMaybeMask; // long類型占8字節(jié)
占8字節(jié)
struct {
explicit_atomic<mask_t> _maybeMask; // int 4字節(jié)
#if __LP64__
uint16_t _flags; // short 占2字節(jié)
#endif
uint16_t _occupied; // short 占2字節(jié)
};
接下來看下 originalPreoptCache
看下 preopt_cache_t
explicit_atomic<preopt_cache_t *> _originalPreoptCache;
/* dyld_shared_cache_builder and obj-C agree on these definitions */
struct preopt_cache_entry_t {
uint32_t sel_offs;
uint32_t imp_offs;
};
/* dyld_shared_cache_builder and obj-C agree on these definitions */
struct preopt_cache_t {
int32_t fallback_class_offset;
union {
struct {
uint16_t shift : 5;
uint16_t mask : 11;
};
uint16_t hash_params;
};
uint16_t occupied : 14;
uint16_t has_inlines : 1;
uint16_t bit_one : 1;
preopt_cache_entry_t entries[];
inline int capacity() const {
return mask + 1;
}
};
最后可計算出cache類的內(nèi)存大小16字節(jié)
, 那么總共需要偏移 8 + 8 + 16 = 32
個字節(jié)乘粒。即首地址平移32位得到 bits
(bit
主要儲存相關(guān)類信息)
創(chuàng)建2個類, SAPerson
繼承NSObject
, SAStudent
繼承SAPerson
p/x SAPerson.class
獲取 SAPerson類首地址得到0x0000000100008260 SAPerson
x/4gx 0x0000000100008260
打印首地址isa指針的內(nèi)存信息,將得到
0x100008260
平移32位得0x100008280
(此處為16進(jìn)制, 逢16進(jìn)1, 32則相當(dāng)于進(jìn)2個16即8260
變?yōu)?code>8280)bits
是這個類型class_data_bits_t bits
, 我們這邊為了讀取數(shù)據(jù)轉(zhuǎn)一下p $1 -> data
通過bits
地址得到bits
數(shù)據(jù), 這里的data看源碼可知是class_rw_t
類型的豌注。留意下指針函數(shù)用->
((XXXX *)
結(jié)構(gòu)), 結(jié)構(gòu)體用.
class_rw_t *data() const {
return bits.data();
}
-
p *$2
打印bits
中數(shù)據(jù)信息
由于bits里面數(shù)據(jù)類型是class_rw_t
, 為了方便探索類里面的屬性, 方法等, 我們接下來看一下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 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);
}
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
源碼可看到, 首先class_rw_t也是個結(jié)構(gòu)體類型, 結(jié)構(gòu)體中有提供相應(yīng)的方法去獲取 properties 屬性列表
、method 方法列表
灯萍、protocols協(xié)議列表
等
那么我們在SAPerson
定義幾個屬性, 方法
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface SAPerson : NSObject {
NSString *hobby;
}
@property (nonatomic, copy) NSString *sa_name;
@property (nonatomic, assign) NSInteger sa_age;
- (void)sayYes;
+ (void)sayNo;
@end
bits 數(shù)據(jù)信息 $3
在之前的例子我上面已經(jīng)講過了, 我們從讀bits數(shù)據(jù)信息$3
之后開始
p $3.properties()
獲得的屬性列表
的list結(jié)構(gòu), 其中list
中的ptr
就是屬性數(shù)組的參數(shù)指針地址轧铁。(p $3.properties()
命令中的propertoes
方法是由class_rw_t提供的, 方法中返回的實際類型為property_array_t
)p *$4.list.ptr
讀一下指針地址, 可獲取內(nèi)存信息, count = 2, 也符合我們建的2個屬性p $5.get(0)
可獲取到sa_name
對應(yīng)屬性(property_t) $6 = (name = "sa_name", attributes = "T@\"NSString\",C,N,V_sa_name")
p $5.get(1)
可獲取到sa_age
屬性(property_t) $7 = (name = "sa_age", attributes = "Tq,N,V_sa_age")
p $6.get(2)
數(shù)組越界, 因為我們只建立了2個屬性
我們接下來看下方法
p $3.methods()
獲得的方法列表
的list結(jié)構(gòu), 接下來跟讀屬性類型, 依次讀取指針地址, 讀取列表對應(yīng)項p $5.get(0).name
讀取出方法名.cxx_destruct
由于底層是C++, 系統(tǒng)默認(rèn)添加的方法可以看到, 有自定義的方法
sayYes
, 系統(tǒng)自動生成的2個屬性的set
,get
方法, 方法列表也有數(shù)組越界, 例如count = 6,p $5.get(6).name
讀的時候也能看見數(shù)組越界報錯
當(dāng)然你要讀協(xié)議列表
的list結(jié)構(gòu), 那里就p $3.protocols()
即可
其實到這里我們會有疑問, 屬性
, 方法``協(xié)議
打印出來了沒問題, 但是成員變量hobby
與類方法sayNo
并沒有打印出來。我們再返回看一下struct class_rw_t
, 里面除了methods
旦棉、properties
齿风、protocols
,還有一個ro
方法 const
class_ro_t *ro(), 看下
ro`底層
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;
...
};
可看到const ivar_list_t * ivars;
, 有一個ivars
屬性, 我們仿照下上面也讀一下ro
可看到成員變量hoppy
儲存在ivar_list_t
里面
總結(jié)
通過
XXXX {}
定義的成員變量
绑洛,會存儲在類的bits
屬性中救斑,通過bits --> data() -->ro() --> ivars
獲取成員變量列表,除了包括成員變量
真屯,還包括屬性
的成員變量通過
@property
定義的屬性
脸候,也會存儲在bits
屬性中,通過bits --> data() --> properties() --> list
獲取屬性列表讨跟,其中只包含property屬性
接下來我們看下類方法存在哪里
x/4g SAPerson.class
獲取類的內(nèi)存信息, 以4片段打印p/x 0x00000001000083b8 & 0x00007ffffffffff8UL
獲取元類的首地址p (class_data_bits_t *)0x00000001000083d8
轉(zhuǎn)成class_data_bits_t
型便于我們獲取bits
信息, 同時別忘了平移32位
, 元類的首地址平移32位
得到bits
信息p $2->data()
通過元類地址獲取bits
信息p *$3
打印bits
數(shù)據(jù)p $4.methods()
獲取元類bits中的方法數(shù)組
p $5.list.ptr
/p *$6
(直接p *$5.list.ptr
也可以) 獲取元類方法數(shù)組
中的方法列表
p $7.get(0).name
讀出方法名, 可看到類方法是儲存在元類中的
總結(jié)
類的
實例方法
儲存在類的bits屬性
中纪他。 系統(tǒng)自動生成自定義屬性@property
的set, get
方法, 也是存在這里。類的
類方法
儲存在元類的bits屬性
中晾匠。