iOS底層探索之類的結(jié)構(gòu)(中)

類的結(jié)構(gòu)探索分析.png

在上一篇博客里面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)存信息

調(diào)試結(jié)果

第一個是isa;第二個是superclass,po 出來是NSObject,JPPerson 是繼承NSObject的;那么以此類推,第三個是cache,第四個是bits

bits

我們要了解bits里面的data信息,該怎么拿呢?光知道一個地址也不行?。磕敲次覀兗热恢懒?code>isa(首地址),是不是就可以通過指針偏移,內(nèi)存偏移拿到呢?那么要偏移多少個呢?

類的結(jié)構(gòu)

要想拿到bits,指針在內(nèi)存中必須要平移指向bits,我知道isa是8個字節(jié)長度,superclass也是8個字節(jié)長度,那么cache_t呢?看看內(nèi)部結(jié)構(gòu)分析下

cache_t

cache_t內(nèi)存大小

分析得到cache_t是16,那么加isasuperclass一共就是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

class_rw_t結(jié)構(gòu)

從沒有錯?。〈蛴〉男畔⒑驮?code>class_rw_t源碼里面的結(jié)構(gòu)是一樣的???
那怎么沒有呢?我們剛打印的是結(jié)構(gòu)信息,里面的方法沒有找到,我們繼續(xù)去看看源碼,看看有沒有打印屬性的方法。
class_rw_t方法

還真有方法,你說巧不巧?。『俸?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>

list_array_tt

知道了結(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

method_t

在源碼里面發(fā)現(xiàn)了method_t結(jié)構(gòu)體,里面嵌套了一個big結(jié)構(gòu)體,big里面有個SEL 還有imp
big

那么可以通過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 MemoryDirty 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

從圖中我們知道成員變量在class_ro_t里面,那么我們打印一下看看

ro

既然里面有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ù)更新

iOS底層探索之類的結(jié)構(gòu)(下)

?? 請動動你的小手,點個贊????

?? 喜歡的可以來一波,收藏+關(guān)注,評論 + 轉(zhuǎn)發(fā),以免你下次找不到我,哈哈????

??歡迎大家留言交流,批評指正,互相學習??,提升自我??

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容