iOS 底層探索:類的結構分析

iOS 底層探索: 學習大綱 OC篇

前言

  • 這篇主要內容探索 類的結構分析。
  • 通過上篇iOS 探索 isa與類關聯的原理,知道了isa關聯了當前的類信息之后,類的屬性、方法列表等存在哪里呢?抱著疑問我們開始類的結構的探索。

準備工作

一、引入元類

我們通過LLDB調試, 先探索類的內存信息。
類的內存分析

注:其實圖中第二個打印的指針指向的HJPerson的元類。

打印的具體流程如下:
打印內存分析圖

那么我們按照上圖的打印分析,我們繼續尋找元類的上層是啥呢?如下圖

探索元類的終點

如圖2、3、4中 打印元類還有根元類NSObject ,圖中3和4的又不一樣,分析可知一個是NSObject類 一個是 NSObject的元類NSObject的元類又是HJPerson的元類根元類, 根源類再次查看地址還是自己,說明已經到底了。

先梳理一下isa指向流程:isa對象——> 類HJPerson ——>元類HJPerson ——> 根元類NSObject <=> 根元類NSObject

拓展:查看根元類的內存

從圖片上可以看出,NSObject的元類、根元類,根根元類的指針地址都是一模一樣的

總結一下對元類的理解:

元類就是類對象所屬的類。所以,實例是類的實例,類作為對象又是元類的實例。OC中所有的類都是一種對象,所以元類也是對象,那么元類是根元類的實例,同時根元類是其自身的實例。

二、 分析isa、對象、類和元類的關系

對象元類跟隨isa指針走向關系簡單流程如下圖

  • 元類也有isa指針,它的isa指針最終指向的是一個根元類(root metaClass);
  • 根元類的isa指針指向本身,這樣形成了一個封閉的內循環;
最終各個類實例變量的繼承關系如圖:
isa經典流程圖

isa流程圖 注:

    1. superClass是一層層集成的,到最后NSObject的superClass是nil。而NSObject的isa指向根元類,這個根元類的isa指向它自己,而它的>superClass是NSObject,也就是最后形成一個環。
    1. metaClass也是相互繼承的。

三、源碼分析 isa的來源:objc_class與objc_object

struct HJPerson_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
};

struct NSObject_IMPL {
    Class isa;
};

查看HJPerson類的編譯后的結構體,我們發現NSObject_IMPL也是個結構體,結構體內部的isa是一個class類型的,說明isa也是一個class的對象,那么class又是啥?

再次查看class 的來源

 //class又是objc_class的對象
typedef struct objc_class *Class;
 //objc_object結構體里面有isa
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
//objc_class 繼承objc_object
struct objc_class : objc_object {
    // Class ISA;                 //默認有個 ISA
    Class superclass;         //父類
    cache_t cache;             //緩存
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() const {
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
    ......

可以看出:

  • Class 是一個objc_class 結構類型的對象,id是一個 objc_object結構類型的對象。
  • objc_object結構體中可以看出來,這個結構體只有一個成員變量,這是一個Class類型的變量isa
  • objc_class又繼承objc_object,其中// Class ISA;,這里其實就是isa指向的終點。這里注釋不是表示沒有,而是代表本身自帶一個isa。
  • objc_class結構體可以看出來,里面有個isa屬性,還有個super_class屬性,它倆都是指針,其實在objc_class的定義中也能看出來,每一個objc_class都有isa,但是不一定會有super_class
總結:
  • 所有的對象 + 類 + 元類 都有isa屬性
  • 所有的對象都是由objc_object繼承來的,所以常說萬物皆對象
  • 在結構層面可以通俗的理解為上層OC 與 底層的對接:
    • 下層是通過 結構體 定義的 模板,例如objc_class、objc_object
    • 上層 是通過底層的模板創建的 一些類型,例如HJPerson

四、針對數組指針進行內存平移拓展

數組指針的平移推導流程:


數組指針

說明:

  • 發現數組地址和數組的值1的地址一樣。說明了數組值的首地址即為數組的地址。并且數組的第二個值的地址 跟第一個值的地址的偏移量是4個字節,也就是int的長度。所以數組中的值的地址是相對平移
    -偏移:指針變量的偏移, 用數組的指針訪問數組元素 可以反向推導數組的值
  • 拓展:類似結構體,我們是否也可以通過地址的偏移拿到類的結構體內部的值呢?

五、類的內存結構分析

  • 通過源碼拿到類對象的結構體 objc_class 在最新版(objc4-781版本)定義
struct objc_class : objc_object {
    // Class ISA; //默認含有一個8字節isa指針
    Class superclass; //父類指針 8字節
    cache_t cache;             // 緩存 16字節
    class_data_bits_t bits;    //類的信息

    class_rw_t *data() const {
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
 .......
  • bits 里面存的是 各種方法協議的信息。通過內存平移原理我們可以通過底層的方法獲取到類的信息。

1. 通過偏移計算類的bits
分析如圖

偏移計算類的bits

注:

32字節的由來

  • 兩個class 都是isa 指針是8+8個字節 這個好理解

  • 我們來看 cache_t cache 為什么是16字節呢?

2.查看源碼分析 cache_t結構體大小

struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    explicit_atomic<struct bucket_t *> _buckets;  //bucket_t * 這是一個指針 8 字節
    explicit_atomic<mask_t> _mask; //mask_t的定義:typedef uint32_t mask_t;  mask_t 是 unsigned int 的別名,占4字節
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    explicit_atomic<uintptr_t> _maskAndBuckets; //typedef unsigned long   uintptr_t;(unsigned long) 占8字節
    mask_t _mask_unused; //mask_t 是 unsigned int 的別名,占4字節
    
    // 以下 static 修飾的變量不在結構體內存中 所以忽略
    static constexpr uintptr_t maskShift = 48;
    static constexpr uintptr_t maskZeroBits = 4;
    static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
    static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << (maskShift - maskZeroBits)) - 1;
    static_assert(bucketsMask >= MACH_VM_MAX_ADDRESS, "Bucket field doesn't have enough bits for arbitrary pointers.");
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    explicit_atomic<uintptr_t> _maskAndBuckets; // uintptr_t;(unsigned long) 占8字節
    mask_t _mask_unused;//mask_t 是 unsigned int 的別名,占4字節
    static constexpr uintptr_t maskBits = 4;
    static constexpr uintptr_t maskMask = (1 << maskBits) - 1;
    static constexpr uintptr_t bucketsMask = ~maskMask;
#else
#error Unknown cache mask storage type.
#endif
    
#if __LP64__
    uint16_t _flags; //unsigned short 2個字節
#endif
    uint16_t _occupied; //unsigned short 2個字節

根據源碼中注釋進行計算:分析if else中的判斷條件
最后結果: 8+4+2+2 = 16

3.通過源碼查看屬性 方法 協議:

通過查看class_rw_t定義的源碼發現,跳轉class_rw_t到源碼中查看,可以看到如圖
image.png

通過lldb查看屬性列表property_list

如下圖
lldb查看屬性列表

打印方法解釋:

  • p $9.properties():命令中的propertoes方法是由class_rw_t提供的,方法中返回的實際類型為property_array_t;
  • p $10.list :list是properties的成員;
  • p *$13:直接獲取property_list_t的指針內容;
  • p $14.get(0):通過get方法獲取屬性;

4.lldb 查看方法列表methods_list

如下圖
lldb查看方法列表打印

注:

  • 類方法+ (void)playByteRoll; 獲取到的 name = ".cxx_destruct"說明了類方法不存在類中,調用類方法需要底層操作一波兒;
  • 對象方法、set 、get方法自動生成在方法列表中。

5.查找類的類方法:

其實元類中儲存著類的類方法 ,在lldb中查找如圖:
lldb類的類方法分析圖.png

6.查找類的成員變量:
我們知道屬性 = 成員變量 + set方法 + get方法,所以我們猜成員變量估計也在類中。
我們在源碼中查找

成員變量查找流程

注:實例變量ivars 是成員變量的一部分。所以ivars 存的就是成員變量
再來通過lldb查找

lldb查找類的成員變量

五、總結

這篇內容思路:
引入元類——>查看isa走勢圖——>isa的指向的根源objc_class——>輸出objc_classbits屬性——>在bits中找到屬性方法成員變量、并在元類中找到類方法——>從而清晰的分析了大致的類的結構

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,382評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,038評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,853評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,616評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,112評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,355評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,869評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,727評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,928評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,165評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,570評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,813評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,585評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,892評論 2 372