前言:
1.我們前面已經對isa的結構進行了分析,但整個isa指向過程是什么呢?
2.我們經常看到的那張經典的isa走位圖又該如何理解呢?
3.前面我們已經知道實例對象的isa指針指向了存儲類信息的類對象,那么這個存儲類信息的類的結構又是怎樣的呢?
使用LLDB探索isa走位
-
首先定義LGHPerson,LGHTeacher類,并創建實例對象,先在main函數里面的objc打個斷點。
準備.png 然后使用lldb, 查看p對象的內存
(lldb) po p
<LGHPerson: 0x1006d1dc0>
(lldb) x/4gx 0x1006d1dc0
0x1006d1dc0: 0x001d800100002181 0x0000000000000000
0x1006d1dd0: 0x726573554b575b2d 0x7320747069726353
- 獲取isa的shiftcls的地址
(lldb) p/x 0x001d800100002181 & 0x00007ffffffffff8ULL
(unsigned long long) $3 = 0x0000000100002180
- 讀取該段地址,與
x LGHPerson.class
比較,發現一致,說明p.isa --> 存儲類信息的類。
(lldb) x 0x0000000100002180
0x100002180: 58 21 00 00 01 00 00 00 40 11 3f 00 01 00 00 00 X!......@.?.....
0x100002190: 30 57 6d 00 01 00 00 00 07 00 00 00 10 80 04 00 0Wm.............
(lldb) x LGHPerson.class
0x100002180: 58 21 00 00 01 00 00 00 40 11 3f 00 01 00 00 00 X!......@.?.....
0x100002190: 30 57 6d 00 01 00 00 00 07 00 00 00 10 80 04 00 0Wm.............
(lldb) x object_getClass(p)
0x100002180: 58 21 00 00 01 00 00 00 40 11 3f 00 01 00 00 00 X!......@.?.....
0x100002190: 30 57 6d 00 01 00 00 00 07 00 00 00 10 80 04 00 0Wm.............
- 接下來我們x/4gx 打印存儲類信息的類對象的內存
(lldb) x/4gx 0x0000000100002180
0x100002180: 0x0000000100002158 0x00000001003f1140
0x100002190: 0x00000001006d5730 0x0004801000000007
- 接下來,我們看看類對象得isa的shiftcls
(lldb) p/x 0x0000000100002158 & 0x00007ffffffffff8ULL
(unsigned long long) $8 = 0x0000000100002158
- 讀取類對象得isa的shiftcls存儲的內存,其實就是元類,系統會自動幫我生成。所以類對象的isa-->元類
(lldb) x/4gx 0x0000000100002158
0x100002158: 0x00000001003f10f0 0x00000001003f10f0
0x100002168: 0x0000000101208fc0 0x0001e03100000007
- 我們繼續那元類的isa,獲取shiftcls
(lldb) p/x 0x00000001003f10f0 & 0x00007ffffffffff8ULL
(unsigned long long) $13 = 0x00000001003f10f0
- 讀取地址 ,并與存儲NSObject類信息的類對象地址對比,發現一致,說明元類的isa-->存儲NSObject類信息的類(根元類)
(lldb) x/4gx 0x00000001003f10f0
0x1003f10f0: 0x00000001003f10f0 0x00000001003f1140
0x1003f1100: 0x000000010070a2f0 0x0005e03100000007
(lldb) x/4gx NSObject.class
0x1003f1140: 0x00000001003f10f0 0x0000000000000000
0x1003f1150: 0x000000010110b1c0 0x0001801000000003
- 我們繼續探索根元類的isa指向,發現指向根元類自己
(lldb) p/x 0x00000001003f10f0 & 0x00007ffffffffff8ULL
(unsigned long long) $16 = 0x00000001003f10f0
(lldb) x/4gx 0x00000001003f10f0
0x1003f10f0: 0x00000001003f10f0 0x00000001003f1140
0x1003f1100: 0x000000010070a2f0 0x0005e03100000007
總結:
1.實例對象的isa -->類對象
2.類對象的isa指向元類對象
3.元類對象的isa指向根元類對象
4.根元類對象的isa指向自身
同理LGHTeacher的走位圖
存儲類信息的類結構
我們看到objc_class定義,但我們發現OBJC2_UNAVAILABLE,這是已經廢棄掉的結構。
// 舊的類結構:
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;
我們繼續尋找objc_class的新結構,我們可以看到objc_class
的新版定義(objc4-781版本)如下,有以下幾個屬性
// 在最新的objc源碼里面:
struct objc_class : objc_object {
// Class ISA; //8字節
Class superclass; //Class 類型 8字節
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
//....方法部分省略,未貼出
}
isa
屬性:繼承自objc_object的isa,占 8字節superclas
屬性:Class類型
,Class
是由objc_object
定義的,是一個指針,占8字節cache
屬性:簡單從類型class_data_bits_t目前無法得知,而class_data_bits_t是一個結構體類型,結構體的內存大小需要根據內部的屬性來確定,而結構體指針才是8字節bits
屬性:只有首地址經過上面3個屬性的內存大小總和的平移,才能獲取到bits
計算 cache_t
結構體的內存大小
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets; // 是一個結構體指針類型,占8字節
explicit_atomic<mask_t> _mask; //是mask_t 類型,而 mask_t 是 unsigned int 的別名,占4字節
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets; //是指針,占8字節
mask_t _mask_unused; //是mask_t 類型,而 mask_t 是 uint32_t 類型定義的別名,占4字節
#if __LP64__
uint16_t _flags; //是uint16_t類型,uint16_t是 unsigned short 的別名,占 2個字節
#endif
uint16_t _occupied; //是uint16_t類型,uint16_t是 unsigned short 的別名,占 2個字節
-
計算前兩個屬性的內存大小,有以下兩種情況,最后的內存大小總和都是12字節
- 【情況一】if流程
-
buckets
類型是struct bucket_t *
,是結構體指針類型,占8字節
-
mask
是mask_t 類型
,而 mask_t 是unsigned int
的別名,占4字節
-
- 【情況二】elseif流程
- 【情況一】if流程
_maskAndBuckets 是uintptr_t類型,它是一個指針,占
8字節
_mask_unused 是mask_t 類型,而 mask_t 是 uint32_t 類型定義的別名,占
4字節
_flags 是uint16_t類型,uint16_t是 unsigned short 的別名,占
2個字節
_occupied 是uint16_t類型,uint16_t是 unsigned short 的別名,占
2個字節
總結:所以最后計算出cache類的內存大小 = 12 + 2 + 2 = 16字節
獲取bits
所以有上述計算可知,想要獲取bits
的中的內容,只需通過類的首地址平移32字節
即可。如下:($1+32)
(lldb) p/x LGHPerson.class
(Class) $0 = 0x00000001000022a0 LGHPerson
(lldb) p/x 0x00000001000022a0
(long) $1 = 0x00000001000022a0
(lldb) p/x (class_data_bits_t *)($1+32)
(class_data_bits_t *) $2 = 0x00000001000022c0
接下來獲取properties()
,得到里面存儲了name屬性
(lldb) p $4.properties()
(const property_array_t) $12 = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x00000001000021a8
arrayAndFlag = 4294975912
}
}
}
(lldb) p $12.list
(property_list_t *const) $13 = 0x00000001000021a8
(lldb) p *$13
(property_list_t) $14 = {
entsize_list_tt<property_t, property_list_t, 0> = {
entsizeAndFlags = 16
count = 1
first = (name = "name", attributes = "T@\"NSString\",&,N,V_name")
}
}
(lldb) p $14.get(0)
(property_t) $15 = (name = "name", attributes = "T@\"NSString\",&,N,V_name")
數組越界,說明只存儲了name
(lldb) p $14.get(1)
Assertion failed: (i < count), function get, file /Users/xiaofeiguan/Desktop/04-isa指向&類初探/03-自己補充/可編譯objc源碼/runtime/objc-runtime-new.h, line 438.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
類信息里面的properties()里面只存儲屬性,沒有存儲成員變量
接下來獲取methods()
,得到-sayHello -setName -name 這個實例方法
(lldb) p $4.methods()
(const method_array_t) $5 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x00000001000020d8
arrayAndFlag = 4294975704
}
}
}
(lldb) p $5.list
(method_list_t *const) $6 = 0x00000001000020d8
(lldb) p *$6
(method_list_t) $7 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 4
first = {
name = "sayHello"
types = 0x0000000100000f42 "v16@0:8"
imp = 0x0000000100000d70 (KCObjc`-[LGHPerson sayHello] at main.m:21)
}
}
}
(lldb) p $7.get(0)
(method_t) $8 = {
name = "sayHello"
types = 0x0000000100000f42 "v16@0:8"
imp = 0x0000000100000d70 (KCObjc`-[LGHPerson sayHello] at main.m:21)
}
(lldb) p $7.get(1)
(method_t) $9 = {
name = ".cxx_destruct"
types = 0x0000000100000f42 "v16@0:8"
imp = 0x0000000100000d80 (KCObjc`-[LGHPerson .cxx_destruct] at main.m:20)
}
(lldb) p $7.get(2)
(method_t) $10 = {
name = "name"
types = 0x0000000100000f58 "@16@0:8"
imp = 0x0000000100000dc0 (KCObjc`-[LGHPerson name] at main.m:15)
}
(lldb) p $7.get(3)
(method_t) $11 = {
name = "setName:"
types = 0x0000000100000f60 "v24@0:8@16"
imp = 0x0000000100000de0 (KCObjc`-[LGHPerson setName:] at main.m:15)
}
數組越界,說明類信息
里面只存儲實例方法
,不存儲類方法
。
(lldb) p $7.get(4)
Assertion failed: (i < count), function get, file /Users/xiaofeiguan/Desktop/04-isa指向&類初探/03-自己補充/可編譯objc源碼/runtime/objc-runtime-new.h, line 438.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.