
在上一篇博客里面iOS底層探索之類的結(jié)構(gòu)(上)已經(jīng)大致的了解了類的結(jié)構(gòu)
類的結(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_data_bits
我們主要探究是class_data_bits_t bits,在bits里面有我們關(guān)心的類的信息。
那么我們怎么拿呢?先看看下面這個JPPerson類
@interface JPPerson : NSObject
{
int age;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *hobby;
- (void)sayHello;
+ (void)sayNB;
@end
@implementation JPPerson
- (instancetype)init{
if (self = [super init]) {
self.name = @"reno";
}
return self;
}
- (void)sayHello{
}
+ (void)sayNB{
}
@end
使用lldb調(diào)試 x/4gx 命令打印了JPPerson這個類的內(nèi)存信息
第一個是
isa;第二個是superclass,po 出來是NSObject,JPPerson 是繼承NSObject的;那么以此類推,第三個是cache,第四個是bits。
bits
我們要了解bits里面的data信息,該怎么拿呢?光知道一個地址也不行?。磕敲次覀兗热恢懒?code>isa(首地址),是不是就可以通過指針偏移,內(nèi)存偏移拿到呢?那么要偏移多少個呢?
要想拿到
bits,指針在內(nèi)存中必須要平移指向bits,我知道isa是8個字節(jié)長度,superclass也是8個字節(jié)長度,那么cache_t呢?看看內(nèi)部結(jié)構(gòu)分析下
cache_t
分析得到
cache_t是16,那么加isa和superclass一共就是32個字節(jié)的長度。
(lldb) x/4gx JPPerson.class
0x100008358: 0x0000000100008380 0x000000010036a140
0x100008368: 0x00000001003623c0 0x0000802800000000
(lldb) p/x 0x100008358+0x20
(long) $1 = 0x0000000100008378
(lldb) p (class_data_bits_t*)0x0000000100008378
(class_data_bits_t *) $2 = 0x0000000100008378
(lldb) p $2->data()
(class_rw_t *) $3 = 0x0000000101b06bc0
(lldb) p *$3
(class_rw_t) $4 = {
flags = 2148007936
witness = 0
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000144
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
好尷尬?。?code>class_rw_t里面沒有看到我們該看到的信息?。?/p>
class_rw_t
從沒有錯?。〈蛴〉男畔⒑驮?code>class_rw_t源碼里面的結(jié)構(gòu)是一樣的???
那怎么沒有呢?我們剛打印的是結(jié)構(gòu)信息,里面的方法沒有找到,我們繼續(xù)去看看源碼,看看有沒有打印屬性的方法。
還真有方法,你說巧不巧?。『俸?br>
那么靚仔,直接調(diào)用
properties()不就可以了嗎!
properties
(lldb) p $3.properties()
(const property_array_t) $5 = {
list_array_tt<property_t, property_list_t, RawPtr> = {
= {
list = {
ptr = 0x0000000100008198
}
arrayAndFlag = 4295000472
}
}
}
Fix-it applied, fixed expression was:
$3->properties()
(lldb)
調(diào)用properties()方法得到property_array_t
property_array_t
class property_array_t :
public list_array_tt<property_t, property_list_t, RawPtr>
{
typedef list_array_tt<property_t, property_list_t, RawPtr> Super;
public:
property_array_t() : Super() { }
property_array_t(property_list_t *l) : Super(l) { }
};
list_array_tt
list_array_tt<property_t, property_list_t, RawPtr>
知道了結(jié)構(gòu),我們就一層一層的往下扒
(lldb) p $3.properties()
(const property_array_t) $5 = {
list_array_tt<property_t, property_list_t, RawPtr> = {
= {
list = {
ptr = 0x0000000100008198
}
arrayAndFlag = 4295000472
}
}
}
Fix-it applied, fixed expression was:
$3->properties()
(lldb) p $5.list
(const RawPtr<property_list_t>) $6 = {
ptr = 0x0000000100008198
}
(lldb) p $6.ptr
(property_list_t *const) $7 = 0x0000000100008198
(lldb) p $7*
error: <user expression 9>:2:1: expected expression
;
^
(lldb) p *$7
(property_list_t) $8 = {
entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 2)
}
(lldb) p $8.get(0)
(property_t) $9 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
(lldb) p $8.get(1)
(property_t) $10 = (name = "hobby", attributes = "T@\"NSString\",C,N,V_hobby")
(lldb)
還有誰?靚仔看到?jīng)]有,JPPerson的屬性打印出來了,我們要的類的信息成功輸出來了!我這該死的魅力??!
methods()
上面打印了屬性,接下來該看看方法了
(lldb) p $3.methods()
(const method_array_t) $15 = {
list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
= {
list = {
ptr = 0x0000000100008098
}
arrayAndFlag = 4295000216
}
}
}
Fix-it applied, fixed expression was:
$3->methods()
(lldb) p $15.list.ptr
(method_list_t *const) $18 = 0x0000000100008098
(lldb) p *$18
(method_list_t) $19 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 6)
}
(lldb) p $19.get(0)
(method_t) $20 = {}
(lldb) p $19.get(0)
(method_t) $21 = {}
(lldb) p $19.get(1)
(method_t) $22 = {}
what ??什么鬼???怎么get打印不出來???別急繼續(xù)探索下去
method_t
在源碼里面發(fā)現(xiàn)了
method_t結(jié)構(gòu)體,里面嵌套了一個big結(jié)構(gòu)體,big里面有個SEL 還有imp那么可以通過
method_t結(jié)構(gòu)體拿到big,就可以獲取到里面的方法,源碼里面也發(fā)現(xiàn)了獲取big方法big()
big()
(lldb) p $19.get(0).big
(method_t::big) $23 = {
name = "sayHello"
types = 0x0000000100003f94 "v16@0:8"
imp = 0x0000000100003cd0 (JPObjcBuild`-[JPPerson sayHello])
}
Fix-it applied, fixed expression was:
$19.get(0).big()
(lldb) p $19.get(1).big
(method_t::big) $24 = {
name = "hobby"
types = 0x0000000100003f8c "@16@0:8"
imp = 0x0000000100003d40 (JPObjcBuild`-[JPPerson hobby])
}
Fix-it applied, fixed expression was:
$19.get(1).big()
(lldb) p $19.get(2).big
(method_t::big) $25 = {
name = "setHobby:"
types = 0x0000000100003f9c "v24@0:8@16"
imp = 0x0000000100003d70 (JPObjcBuild`-[JPPerson setHobby:])
}
Fix-it applied, fixed expression was:
$19.get(2).big()
(lldb) p $19.get(3).big
(method_t::big) $26 = {
name = "init"
types = 0x0000000100003f8c "@16@0:8"
imp = 0x0000000100003c70 (JPObjcBuild`-[JPPerson init])
}
Fix-it applied, fixed expression was:
$19.get(3).big()
(lldb) p $19.get(4).big
(method_t::big) $27 = {
name = "name"
types = 0x0000000100003f8c "@16@0:8"
imp = 0x0000000100003ce0 (JPObjcBuild`-[JPPerson name])
}
Fix-it applied, fixed expression was:
$19.get(4).big()
(lldb) p $19.get(5).big
(method_t::big) $28 = {
name = "setName:"
types = 0x0000000100003f9c "v24@0:8@16"
imp = 0x0000000100003d10 (JPObjcBuild`-[JPPerson setName:])
}
Fix-it applied, fixed expression was:
$19.get(5).big()
干的漂亮!哈哈??,方法列表里面count值為6,一共有6個都打印了出來了,包括setter方法和getter方法,靚仔就問你服不服!
那么有的靚仔肯定不服了,我怎么沒有看到類方法(+方法)和成員變量打印出來呢?你在這里裝什么大一瓣蒜??!
好,不服是吧!那么我們接著往下探索。
ivars
屬性和成員變量在內(nèi)存中存放的位置是不一樣的,在WWDC2020里面介紹了Clean Memory 和Dirty Memory
Clean Memory
clean memory加載后不會發(fā)生改變的內(nèi)存class_ro_t就屬于clean memory,因為它是只讀的不會,不會對齊內(nèi)存進行修改clean memory
是可以進行移除的,從而節(jié)省更多的內(nèi)存空間,因為如果你有需要clean memory,系統(tǒng)可以從磁盤中重新加載
Dirty Memory
dirty memory是指在進程運行時會發(fā)生改變的內(nèi)存 類結(jié)構(gòu)一經(jīng)使用就會變成dirty memory,因為運行時會向它寫入新的數(shù)據(jù)。例如創(chuàng)建一個新的方法緩存并從類中指向它,初始化類相關(guān)的子類和父類dirty memory是類數(shù)據(jù)被分成兩部分的主要原因
dirty memory要比clean memory昂貴的多,只要程序運行它就必須一直存在,通過分離出那些不會被改變的數(shù)據(jù),可以把大部分的類數(shù)據(jù)存儲為clean memory
請看下面這個圖(WWDC2020視頻里面截取的)
從圖中我們知道成員變量在
class_ro_t里面,那么我們打印一下看看
既然里面有ivars成員信息,那么再打印出來看看
(lldb) p $6.ivars
(const ivar_list_t *const) $7 = 0x0000000100008130
(lldb) p *$7
(const ivar_list_t) $8 = {
entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 3)
}
(lldb) p $7.get(0)
(ivar_t) $9 = {
offset = 0x0000000100008340
name = 0x0000000100003f2a "age"
type = 0x0000000100003f7e "i"
alignment_raw = 2
size = 4
}
Fix-it applied, fixed expression was:
$7->get(0)
(lldb) p $7.get(1)
(ivar_t) $10 = {
offset = 0x0000000100008348
name = 0x0000000100003f2e "_name"
type = 0x0000000100003f80 "@\"NSString\""
alignment_raw = 3
size = 8
}
Fix-it applied, fixed expression was:
$7->get(1)
(lldb) p $7.get(2)
(ivar_t) $11 = {
offset = 0x0000000100008350
name = 0x0000000100003f34 "_hobby"
type = 0x0000000100003f80 "@\"NSString\""
alignment_raw = 3
size = 8
}
Fix-it applied, fixed expression was:
$7->get(2)
(lldb)
成員信息的數(shù)據(jù)類型,名稱、內(nèi)存大小,都打印出來了。
類方法
在上面的測試中,JPPerson的對象方法可以正常打印出來,是在JPPerson.class中獲取打印的。在MachOView中,也是可以看到,類方法確實存在的。
那么類方法也就是加號方法,是否是在元類中的呢?對象方法在類中,類方法在元類中,這不是很符合邏輯嗎?好,那就去探索驗證一下
(lldb) x/4gx JPPerson.class
0x100008358: 0x0000000100008380 0x000000010036a140
0x100008368: 0x00000001003623c0 0x0000802800000000
(lldb) p/x 0x0000000100008380 + 0x20
(long) $19 = 0x00000001000083a0
(lldb) p/x (class_data_bits_t*)0x00000001000083a0
(class_data_bits_t *) $20 = 0x00000001000083a0
(lldb) p $20->data()
(class_rw_t *) $21 = 0x0000000101337080
(lldb) p *$21
(class_rw_t) $22 = {
flags = 2684878849
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4315117329
}
}
firstSubclass = 0x00000001000083a8
nextSiblingClass = 0x00007fff80111eb0
}
(lldb) p $22.methods()
(const method_array_t) $23 = {
list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
= {
list = {
ptr = 0x0000000100008208
}
arrayAndFlag = 4295000584
}
}
}
(lldb) p $23.list.ptr
(method_list_t *const) $24 = 0x0000000100008208
(lldb) p *$24
(method_list_t) $25 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 1)
}
(lldb) p $25.get(0).big()
(method_t::big) $26 = {
name = "sayNB"
types = 0x0000000100003f94 "v16@0:8"
imp = 0x0000000100003da0 (JPObjcBuild`+[JPPerson sayNB])
}
(lldb)
哈哈,還有誰
這波操作,就問你服不服!
總結(jié)
- 元類isa指向:元類isa->根元類isa->根元類(NSObject的元類)
- 元類繼承關(guān)系:類繼承isa->根元類isa->NSObject->nil
- 類中有isa、superclass、chche、bits 成員變量,
- bits 存儲著屬性列表、方法列表、成員變量列表、協(xié)議列表等
更多內(nèi)容持續(xù)更新
?? 請動動你的小手,點個贊????
?? 喜歡的可以來一波,收藏+關(guān)注,評論 + 轉(zhuǎn)發(fā),以免你下次找不到我,哈哈????
??歡迎大家留言交流,批評指正,互相學習??,提升自我??