準備
需要使用到編譯的objc源碼,objc4可下載词顾,編輯可執(zhí)行程序可參考最新macOS 10.15下objc4-779.1源碼編譯調(diào)試。以下是基于Mac OS平臺運行碱妆,ISA_BITFIELD
為在x86架構(gòu)下進行取值肉盹。
isa
指向
創(chuàng)建一個LGPerson
類的實例化對象person
,以16進制打印person
地址疹尾,0x0000000100753450
是當前對象的地址上忍,x/4gx person
格式化輸出person
對象的內(nèi)存骤肛,我們知道首地址為isa
,將isa
的地址和ISA_MASK
按位與運算窍蓝,可得到0x00000001000080e8
地址腋颠,po該地址得到輸出為LGPerson
,從而得到ISA_MASK 0x00007ffffffffff8ULL
的作用是舍去低三位和高17位吓笙,從而獲取中間的44位即shiftcls
淑玫,在之前我們知道shiftcls
是存儲了類指針
的值。再次16進制輸出LGPerson
類自身時得到地址0x00000001000080e8
面睛,由此可知對象的isa
指向是類絮蒿。
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
}
return 0;
}
再次格式化輸出LGPerson
得到isa
,然后將isa & ISA_MASK
按位與計算后得到shiftcls 0x00000001000080c0
叁鉴,可以得到輸出也是LGPerson
土涝,從而可以得到此時LGPerson
為元類。
-
元類
的定義和創(chuàng)建是由編譯器自動完成幌墓,實例對象的isa
指向類對象
,類對象
的isa
指向元類
但壮。作用是將實例方法和類方法進行分區(qū)存放,實例方法存在于類中克锣,類方法存在于元類中
再次格式化輸出LGPerson
得到isa
茵肃,然后將isa & ISA_MASK
按位與計算后得到shiftcls 0x000000010034c0f0
,可以得到輸出也是NSObject
袭祟,然后以16進制打印NSObject.class
后得到地址為0x000000010034c140
,類型為NSObject
的類捞附,此時NSObject
被稱為根元類
巾乳。從而可以得到如下圖中的流程:
- 從圖中總結(jié)繼承流程:繼承只存在于類中,子類繼承自父類鸟召,父類繼承自根類(NSObject)胆绊,NSObject繼承自nil,類和元類只存在
isa
指向關(guān)系欧募,不存在繼承關(guān)系压状,子元類繼承自父元類,父元類繼承自根元類跟继,根元類繼承自NSObject种冬。
類的內(nèi)存分布
-
objc_object
是C/C++編寫的結(jié)構(gòu)體 ,是所有類的最低層的實現(xiàn)舔糖,包含了一個isa
屬性娱两。 -
objc_class
繼承自objc_object
是所有自定義類的基礎(chǔ),在objc_class
中主要有繼承自objc_object
的isa
金吗,Class superclass
十兢、cache_t cache
和class_data_bits_t bits
趣竣,類中的屬性,實例方法和協(xié)議都被保存在bits
旱物。
對于類的屬性我們可以使用內(nèi)存平移
的方式進行訪問.
struct objc_class : objc_object {
// Class ISA; 8字節(jié)
Class superclass; 8字節(jié)
cache_t cache; 16字節(jié)
class_data_bits_t bits;
class_rw_t *data() const {
return bits.data();
}
}
以上是ISA
和superclass
是容易理解的都是Class
類型遥缕,占8字節(jié)。主要就是cache
的大小宵呛。
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets;
explicit_atomic<mask_t> _mask;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
由于static
靜態(tài)類型數(shù)據(jù)不再類中单匣,所以cache_t
中只有上面一些數(shù)據(jù)占用了內(nèi)存,buckets
為bucket_t
類型其中的_imp
為unsigned long uintptr_t
占用8字節(jié)烤蜕,(unsigned int uint32_t)mask_t
類型的_mask
封孙。而類中的屬性,實例方法和協(xié)議在class_rw_t
中讽营,如下代碼:
struct class_rw_t {
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
中包含了methods
虎忌、properties
和protocols
屬性,所以可以看出關(guān)于類中的屬性橱鹏,方法和協(xié)議都在class_rw_t *data()
中膜蠢。
拓展
objc_object
和對象
的關(guān)系
對象是以objc_object
為模板創(chuàng)建而來的,NSObject
為OC層結(jié)構(gòu)莉兰,objc_object
為C/C++
層的結(jié)構(gòu)體挑围。
指針平移
int a[4] = {1, 2, 3, 4};
int *b = a;
for (int i = 0; i < 4; i++) {
NSLog(@"索引取值:%d", a[i]);
NSLog(@"指針取值:%d", *(b+i));
}
聲明一個int
數(shù)組a
,聲明一個指針b
指向數(shù)組a
糖荒,然后分別使用數(shù)組下標和指針來輸出數(shù)據(jù)杉辙,得出如下結(jié)果:
因此我們可以看出在輸出數(shù)組數(shù)據(jù)時我們既可以使用下標輸出,也可以使用指針來輸出捶朵。在代碼中蜘矢,
*b = a
的含義是指針b
指向了數(shù)組a
的首地址,即數(shù)組a的第一個元素所在的地址综看,因此只需要移動指針品腹,就可以得到整個數(shù)組的元素。由此可以類推到類中红碑。當我們使用
p/x
打印類類型和地址舞吭,該地址即為首地址,所以我們可以使用內(nèi)存平移的方式來訪問類的內(nèi)存情況析珊。