準(zhǔn)備工作
內(nèi)存偏移
普通指針代碼分析:
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 10;
int b = 10;
int *a_p = &a;
int *b_p = &b;
NSLog(@"%d -- %p -- %p",a,&a,&a_p);
NSLog(@"%d -- %p -- %p",b,&b,&b_p);
}
//打印結(jié)果
內(nèi)存偏移[86667:1854956] 10 -- 0x7ffeefbff47c -- 0x7ffeefbff470
內(nèi)存偏移[86667:1854956] 10 -- 0x7ffeefbff478 -- 0x7ffeefbff468
- 好明顯
a
和b
雖然值是一樣的,但是它們的地址卻不一樣蓬衡。這就是我們常說的深拷貝咯
掉奄。 -
a
和b
的地址剛好相差了4
個(gè)字節(jié)箱歧,這取決于a
的的類型。 - 地址大小比較:
a > b > a_p > b_p
栓拜,由于是局部變量座泳,他們都存放在棧區(qū)
!
注意:棧區(qū)的地址是由高到低的幕与。
對(duì)象指針代碼分析:
int main(int argc, const char * argv[]) {
@autoreleasepool {
XXPerson *p1 = [LGPerson alloc];
XXPerson *p2 = [LGPerson alloc];
NSLog(@"%@ -- %p",p1,&p1);
NSLog(@"%@ -- %p",p2,&p2);
//打印結(jié)果
內(nèi)存偏移[86667:1854957] <LGPerson: 0x100796fb0> -- 0x7ffeefbff460
內(nèi)存偏移[86667:1854957] <LGPerson: 0x10078b730> -- 0x7ffeefbff458
}
-
p1
和p2
的內(nèi)存地址是不一樣的挑势,alloc
開辟的內(nèi)存在堆區(qū)
。 -
&p1
和&p2
的內(nèi)存地址也不一樣的啦鸣,好明顯是指向了兩個(gè)不同的地址潮饱。
注意:堆區(qū)的地址是由低到高的。
數(shù)組指針代碼分析:
int main(int argc, const char * argv[]) {
@autoreleasepool {
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++) {
int value = *(d+i);
NSLog(@"%d",value);
}
}
//打印結(jié)果
內(nèi)存偏移[86667:1854956] 0x7ffeefbff490 - 0x7ffeefbff490 - 0x7ffeefbff494
內(nèi)存偏移[86667:1854956] 0x7ffeefbff490 - 0x7ffeefbff494 - 0x7ffeefbff498
//循環(huán)打印結(jié)果
內(nèi)存偏移[86667:1854956] 1
內(nèi)存偏移[86667:1854956] 2
內(nèi)存偏移[86667:1854956] 3
內(nèi)存偏移[86667:1854956] 4
- 數(shù)組的地址就是數(shù)組元素內(nèi)存中的
首地址
诫给,&c,&c[0]
指向同一個(gè)地址香拉。 - 數(shù)組中元素地址的間隔是
元素?cái)?shù)據(jù)類型
決定的啦扬。 - 數(shù)組里面的元素可以通過
地址+n
取址的方式取出來,如*(d+i)
; - 數(shù)組元素不相同用
首地址+偏移量方式
凫碌,根據(jù)當(dāng)前變量的偏移值(需要前面類型大小相加)
圖解如下:
類isa走位的分析
通過之前的文章可以知道對(duì)象本質(zhì)是結(jié)構(gòu)體
扑毡,結(jié)構(gòu)體的第一個(gè)成員變量就是isa
。那么類的結(jié)構(gòu)是什么有什么证鸥?類有isa指向嘛僚楞?如果有他們之間的關(guān)系是怎么樣的勤晚?那么針對(duì)這些問題我們進(jìn)行以下的分析枉层。
isa的走位圖(官方的)
iOS不同架構(gòu)下的isa掩碼
- 拿到類的信息我之前總結(jié)過有三種,用掩碼來獲取是比較快的而且直接的赐写。
- 從
objc4
的源碼可以拿到:
x86_64:define ISA_MASK 0x00007ffffffffff8ULL
arm64:define ISA_MASK 0x0000000ffffffff8ULL
arm64(simulators):define ISA_MASK 0x007ffffffffffff8ULL
類對(duì)象的內(nèi)存?zhèn)€數(shù)
int main(int argc, char * argv[]) {
@autoreleasepool {
Class class1 = [MyPersion class];
Class class2 = [MyPersion alloc].class;
Class class3 = object_getClass([MyPersion alloc]);
Class class4 = [MyPersion alloc].class;
NSLog(@"\n-%p-\n-%p-\n-%p-\n-%p-",class1,class2,class3,class4);
}
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
打印結(jié)果:
2021-06-18 15:57:27.779872+0800 test[1355:490263]
-0x102e21818-
-0x102e21818-
-0x102e21818-
-0x102e21818-
得出結(jié)論:類的內(nèi)存地址分配都是一樣的鸟蜡,每個(gè)類只有一個(gè)內(nèi)存塊,這根對(duì)象的內(nèi)存分配不一樣挺邀。
對(duì)象的類isa指向(元類的引出)
我自己創(chuàng)建了一個(gè)persion類繼承于NSObject類揉忘,代碼分析如下:
分析:
-
0x0000000100ea1a30
是XXPersion
類的地址,當(dāng)我們找出XXPersion
類的isa
指向的類的地址是0x0000000100ea1a08
端铛,也同樣與指向了XXPersion
類泣矛。上面已經(jīng)說到了類只會(huì)開辟一個(gè)內(nèi)存空間,那么現(xiàn)在是不是矛盾了禾蚕?還是其中有一個(gè)不是XXpersion
類您朽? - 其不然,
0x0000000100ea1a30
輸出的是XXPersion
類的地址换淆,0x0000000100ea1a08
指向是XXPersion
的元類
哗总。
根元類的引出
參照以上的想法,元類會(huì)不會(huì)也有isa倍试,isa指向是什么讯屈?很簡單,那就實(shí)踐實(shí)踐一下唄县习!請以下操作:
分析:
-
0x00000001e7afb260
地址是XXPersion
的元類isa
指向的類地址涮母,發(fā)現(xiàn)是NSObject
類,然后再找NSObject
類的isa
指向哪個(gè)類躁愿,發(fā)現(xiàn)地址還是0x00000001e7afb260
叛本,并指向了自己。
總結(jié):
經(jīng)過上面代碼的層層分析攘已,我們驗(yàn)證么isa的走位圖的isa走位流程:objc(對(duì)象) --> class(類) --> metaClass(元類) --> rootMetaClass(根元類) --> rootMetaClass(根元類自己)炮赦。
isa走位圖:
類的繼承鏈分析
用oc代碼看看繼承鏈的原理,創(chuàng)建XXTeacher類繼承于XXPerson類,如下圖:
分析:
NSObject
的父類打印的結(jié)果是nil
样勃。XXTeacher
的元類的父類是XXPerson
的元類(XXPerson
的元類的地址和XXPerson
類的地址不一樣)吠勘。XXPerson
的元類的父類是NSObject
的元類性芬,NSObject
的元類的父類是NSObject
(和NSObject
類的地址一樣)
-
XXTeacher
繼承XXPerson
,XXPerson
繼承NSObject
剧防,NSObject
的父類是nil
-
XXTeacher
元類 繼承XXPerson
元類植锉,XXPerson
繼承 根元類,根元類繼承NSObject
類之間的繼承流程圖如下:
官方isa走位圖和繼承圖的還原:
類的結(jié)構(gòu)分析
查看objc4(818版本)
的源碼峭拘,找到了objc_class
結(jié)構(gòu)如下:
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)
# if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
if (superclass == Nil)
return Nil;
......... //源碼位置為objc-runtime-new.h文件第1688行-2173行
//省略了好多代碼
分析
- 類的結(jié)構(gòu)中也有隱藏的
isa
俊庇,占用8
個(gè)字節(jié) -
Class superclass
是類的父類,占用8
個(gè)字節(jié) -
cache_t cache
是類的緩存空間鸡挠,占用16個(gè)
字節(jié) -
class_data_bits_t
保存類的數(shù)據(jù)辉饱,如屬性
、方法
等信息拣展。
探究cache_t
內(nèi)存大小
我們在開發(fā)過程中看類主要看類的屬性和方法彭沼,上面所述類的屬性和方法都存放在class_data_bits_t
結(jié)構(gòu)體中,那么我們必須要知道class_data_bits_t
結(jié)構(gòu)體的地址备埃,所以分析cache_t
結(jié)構(gòu)體內(nèi)存大小是非常必要的姓惑。上代碼:
struct cache_t {
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask; //占用8個(gè)字節(jié)
union { //聯(lián)合體大小只關(guān)心內(nèi)存最大的成員
struct {
explicit_atomic<mask_t> _maybeMask; //占用4個(gè)字節(jié)
#if __LP64__
uint16_t _flags; //占用2個(gè)字節(jié),現(xiàn)在的objc版本只能進(jìn)入這個(gè)判斷
#endif
uint16_t _occupied; //占用2個(gè)字節(jié)
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache; //占用8個(gè)字節(jié)
};
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
// _bucketsAndMaybeMask is a buckets_t pointer
// _maybeMask is the buckets mask
static constexpr uintptr_t bucketsMask = ~0ul;
static_assert(!CONFIG_USE_PREOPT_CACHES, "preoptimized caches not supported");
.........忽略與結(jié)構(gòu)體內(nèi)存無關(guān)的代碼
//源碼位置為objc-runtime-new.h文件第338行-550行
分析:
- 結(jié)構(gòu)體中
static
修飾的靜態(tài)變量按脚、調(diào)用的方法
已經(jīng)其他相關(guān)的運(yùn)算
都不占用結(jié)構(gòu)體的內(nèi)存于毙,所以我省略了好多的代碼。 -
typedef unsigned long uintptr_t
是無符號(hào)長整形辅搬,占用8
個(gè)字節(jié)唯沮。 -
preopt_cache_t *
是結(jié)構(gòu)體指針,占用8
個(gè)字節(jié)伞辛。 -
uint16_t
是無符號(hào)16位整形烂翰,占用2
個(gè)字節(jié)。 -
mask_t
是uint32_t
類型的蚤氏,占用4
個(gè)字節(jié)甘耿。
cache_t
的內(nèi)存大小為:uintptr_t
內(nèi)存大小8
個(gè)字節(jié)+union
內(nèi)存大小8
個(gè)字節(jié) =16
字節(jié)
分析class_data_bits_t bits
結(jié)構(gòu)體
綜上所述,class_data_bits_t bits
結(jié)構(gòu)體記錄的是類的屬性竿滨、成員變量以及方法佳恬。所以必須要了解結(jié)構(gòu)體里面有什么哦!上代碼:
struct class_data_bits_t {
friend objc_class;
// Values are the FAST_ flags above.
uintptr_t bits;
private:
bool getBit(uintptr_t bit) const
{
return bits & bit;
}
// Atomically set the bits in `set` and clear the bits in `clear`.
// set and clear must not overlap.
void setAndClearBits(uintptr_t set, uintptr_t clear)
{
ASSERT((set & clear) == 0);
uintptr_t newBits, oldBits = LoadExclusive(&bits);
//此處省略部分代碼
public:
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
void setData(class_rw_t *newData)
{
//此處省略部分代碼
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 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 { //獲取協(xié)議的方法
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};
}
//此處省略部分代碼
分析:
是不是很迷茫于游?當(dāng)我們看到那么多代碼時(shí)候該怎么做毁葱?首先我們需要看這個(gè)結(jié)構(gòu)提供了什么方法跟屬性,這是非常重要的7“G憬恕!
-
class_rw_t
是結(jié)構(gòu)體類型,提供了獲取屬性列表
前痘,方法列表
凛捏,協(xié)議列表
的方法。通過實(shí)例來驗(yàn)證下方法芹缔,屬性坯癣,變量是不是在class_rw_t
中,在XXPerson
類中添加屬性和方法 以及成員變量最欠,請繼續(xù)往下看驗(yàn)證流程示罗。
獲取類的屬性、方法操作流程分析
獲取類的屬性
1.創(chuàng)建好XXPersion類芝硬,如圖所示:
2.斷點(diǎn)蚜点,進(jìn)行l(wèi)ldb調(diào)試,如圖所示:
步驟:
-
x/4gx XXPerson.class
格式化輸出XXPerson.class
吵取,拿到類的首地址:0x0000000100004530
-
p/x 0x0000000100004530 + 0x20
首地址偏移32
個(gè)字節(jié)(ISA8字節(jié)禽额、superclass8字節(jié)、cache16字節(jié))
皮官,拿到類對(duì)象屬性地址 -
p (class_data_bits_t *)0x0000000100004550
將地址轉(zhuǎn)化成class_data_bits_t
類型,為了使用class_data_bits_t
的函數(shù) -
p $21->data()
使用class_data_bits_t
的data()
函數(shù),拿到class_rw_t
類型的地址 -
p $22->properties()
通過properties()
函數(shù)獲取XXPerson
的成員變量 -
p $23.list
和p $24.ptr
解析出property_list_t
的地址 -
p *$25
通過取地址的方式獲取成員變量property_list_t
-
p $26.get(0)
與p $26.get(1)
通過c++函數(shù)單個(gè)獲取類的成員變量name
实辑、nickName
捺氢、age
注意: - 最后獲取屬性的
get()
方法是迭代器
,系統(tǒng)自帶的方法剪撬。 - 發(fā)現(xiàn)
屬性列表中
只有3
個(gè)摄乒,那么定義的hobby
成員變量去哪里了? -
方法列表
的方法也是同樣獲取的残黑,請接著往下操作馍佑。
獲取類的方法
步驟:
NSObject.class
-> class_data_bits_t
-> class_rw_t
-> method_array_t
-> method_list_t
-> method_t
-> big
成員變量與類方法的獲取
成員變量獲取
觀察發(fā)現(xiàn)class_rw_t
還有一個(gè)獲取class_ro_t *
的方法,會(huì)不會(huì)在class_ro_t
中梨水,源碼查看class_ro_t
的類型拭荤。
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; //存儲(chǔ)成員變量
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
//省略了部分代碼(objc-runtime-new.h文件第1037行-1171行)
}
分析:
-
class_ro_t
是結(jié)構(gòu)體類型,有一個(gè)ivar_list_t * ivars
變量 -
ivar_list_t * ivars
變量存儲(chǔ)著類的成員變量疫诽。 - 系統(tǒng)會(huì)給屬性自動(dòng)生成一個(gè)帶
_屬性名變量
舅世,存儲(chǔ)在class_ro_t
中的變量列表里
lldb調(diào)試
步驟: -
x/4gx LGPerson.class
格式化輸出LGPerson.class
,獲取到首地址 -
p/x 0x100008408 + 0x20
首地址偏移32
字節(jié)(ISA8字節(jié)奇徒、superclass8字節(jié)雏亚、cache_t16字節(jié))
,拿到包含類屬性方法成員變量的對(duì)象class_data_bits_t
的地址 -
p (class_data_bits_t *)$1
將地址轉(zhuǎn)換為class_data_bits_t
摩钙,為了使用class_data_bits_t
的函數(shù) -
p $2->data()
使用class_data_bits_t的data()
函數(shù),拿到class_rw_t
類型的地址 -
p $3->ro
使用class_rw_t
的ro
函數(shù)罢低,拿到class_ro_t
類型的地址 -
p *$4
取lass_ro_t
類型地址的值,拿到了class_ro_t
對(duì)象 -
p $5.ivars
使用ivars
函數(shù)獲取class_ro_t
對(duì)象的ivars
,得到了指向ivar_list_t
地址的指針 -
p *$6
通過取地址的方式獲實(shí)例變量數(shù)組ivar_list_t,entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 4)
,可以看到有4
個(gè)ivars
- 通過
c++
函數(shù)get()
單個(gè)獲取類的實(shí)例方法:
p $7.get(0):
(ivar_t) $8 = {
offset = 0x0000000100008370
name = 0x0000000100003eec "hobby"
type = 0x0000000100003f60 "@"NSString""
alignment_raw = 3
size = 8
}
ivars獲取流程源碼:NSObject.class
->class_data_bits_t
->class_rw_t
->class_ro_t
->ivar_list_t
->ivar_t
類方法獲取
對(duì)象的方法是存儲(chǔ)在類中胖笛,那么類方法可能存儲(chǔ)在元類
中网持。那么實(shí)踐一下咯宜肉。
lldb調(diào)試
步驟:
-
x/4gx XXPerson.class
格式化打印類XXPerson
,得到類的首地址 -
p/x 0x00000001000083e0 & 0x00007ffffffffff8
將isa
指針和ISA_MASK
做與操作翎碑,拿到XXPerson的metaClass(元類)
-
x/4gx 0x00000001000083e0
,格式化打印XXPerson
的metaClass(元類)
谬返,拿到元類的首地址 -
p/x 0x1000083e0 + 0x20
,將元類的首地址偏移32個(gè)
字節(jié)(ISA8字節(jié)日杈、superclass8字節(jié)遣铝、cache_t16字節(jié))
,那多元類的class_data_bits_t
對(duì)象地址 -
p (class_data_bits_t *)0x0000000100008400
將地址轉(zhuǎn)化為class_data_bits_t
對(duì)象莉擒,方便調(diào)用函數(shù) -
p $3->data()
調(diào)用class_data_bits_t
的data
函數(shù)酿炸,拿到class_rw_t
對(duì)象 -
p $4->methods()
獲取class_rw_t
的methods
方法列表 -
p $5.list
和p $5.ptr
拿到指向method_list_t
地址的指針 -
p *$7
取地址,拿到了method_list_t
對(duì)象,count
為1
涨冀,entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier>
= (entsizeAndFlags = 27, count = 1), 有一個(gè)類方法
通過c++
函數(shù)get()
與big()
單個(gè)獲取類的類方法:
p $7.get(0).big():
(method_t::big) $9 = {
name = "sayNB"
types = 0x0000000100003f74 "v16@0:8"
imp = 0x0000000100003ce0 (KCObjcBuild+[XXPerson sayNB]) } **類方法獲取流程:
NSObject.class->
metaClass->
class_data_bits_t->
class_rw_t->
method_array_t->
method_list_t->
method_t->
big`*
總結(jié):
附上類探究的整個(gè)流程圖:
學(xué)習(xí)過程的確艱辛填硕!但是學(xué)到知識(shí)讓我非常興奮!通過對(duì)類結(jié)構(gòu)的深入學(xué)習(xí)鹿鳖,使得我對(duì)底層的理解更加深刻了扁眯,自身的專業(yè)知識(shí)又有了進(jìn)一步的提升。讓我們繼續(xù)加油吧????翅帜!