Objective-C Class Ivar Layout 探索

發表於2015-09-15

這次探索源於一個朋友問的問題,當我們定義一個類的例項變數的時候,可以指定其修飾符:

這使得 ivar (instance variable) 可以像屬性一樣在 ARC 下進行正確的引用計數管理。
那麼問題來了,假如這個類是動態生成的:

該如何像上面一樣來新增 ivar 的屬性修飾符呢?
刨根問底了一下,發現 ivar 的修飾資訊存放在了 Class 的 Ivar Layout 中:

ivarLayout 和 weakIvarLayout 分別記錄了哪些 ivar 是 strong 或是 weak,都未記錄的就是基本型別和 __unsafe_unretained 的物件型別。

這兩個值可以通過 runtime 提供的幾個 API 來訪問:

但我們幾乎沒可能用到這幾個 API,IvarLayout 的值由 runtime 確定,沒必要關心它的存在,但為了解決上述問題,我們試著破解了 IvarLayout 的編碼方式。

舉個例子說明,若類定義為:

則儲存 strong ivar 的 ivarLayout 的值為 0x012000
儲存 weak ivar 的 weakIvarLayout 的值為 0x1200

一個 uint8_t 在 16 進位制下是兩位,所以編碼的值每兩位一對兒,以上面的 ivarLayout 為例:

  1. 前兩位 01 表示有 0 個非 strong 物件和 1 個 strong 物件
  2. 之後兩位 20 表示有 2 個非 strong 物件和 0 個 strong 物件
  3. 最後兩位 00 為結束符,就像 cstring 的  一樣

同理,上面的 weakIvarLayout:

  1. 前兩位 12 表示有 1 個非 weak 物件和接下來連續 2 個 weak 物件
  2. 00 結束符

這樣,用兩個 layout 編碼值就可以排查出一個 ivar 是屬於 strong 還是 weak 的,若都沒有找到,就說明這個物件是 unsafe_unretained.

做個練習,若類定義為:

則儲存 strong ivar 的 ivarLayout 的值為 0x012100
儲存 weak ivar 的 weakIvarLayout 的值為 0x01211000

於是乎將 class 的建立程式碼增加了兩個 ivarLayout 值的設定:

本以為解決了這個問題,但是 runtime 繼續打臉,strong 和 weak 的記憶體管理並沒有生效,繼續研究發現, class 的 flags 中有一個標記位記錄這個類是否 ARC,正常編譯的類,且標識了 -fobjc-arc flag 時,這個標記位為 1,而動態建立的類並沒有設定它。所以只能繼續黑魔法,執行時把這個標記位設定上,探索過程不贅述了,實現如下:

把這個 fixup 放在 objc_registerClassPair(class); 之後,這個動態的類終於可以像靜態編譯的類一樣操作 ivar 了,可以測試一下:

Done.

相關文章