OC底層原理04:isa走向&類結構分析

類 isa 走向分析

元類

OC 中類也是一個對象,它的指針也會指向它所屬的類,這樣的類就是元類(MetaClass).元類的定義和建立,都是由編譯器自動完成的.面試

類在底層都會被編譯爲結構體,全部的結構體都「繼承」了 NSObject 結構體的Class isa,因此全部的類都包含了 isa,isa 指針指向了其所屬的類.實例對象的 isa 指向&歸屬於類對象,類對象的 isa 指向&歸屬它的元類.實例對象->類對象->元類緩存

isa 走向

上篇文章,咱們看到 alloc 出來的實例對象中,isa 位域 shiftcls 保存的就是類的指針值,咱們深刻看下,類的 isa 指針,都指向了哪裏呢?markdown

//Code
@interface LGPerson : NSObject @property(nonatomic, copy)NSString *kc_name; - (void)setNB; @end  @implementation LGPerson - (void)setNB{  } @end   LGPerson *person = [LGPerson alloc];  // DeBug (lldb) x/4gx person // 實例對象 0x10182cc50: 0x001d8001000021c9 0x0000000000000000 0x10182cc60: 0x63756f54534e5b2d 0x6f6c6f4372614268 (lldb) p/x 0x001d8001000021c9 & 0x00007ffffffffff8ULL (unsigned long long) $67 = 0x00000001000021c8 (lldb) po 0x00000001000021c8 LGPerson // 類(類對象)  (lldb) x/4gx 0x00000001000021c8 0x1000021c8: 0x00000001000021a0 0x0000000100334140 0x1000021d8: 0x000000010032e430 0x0000801000000000 (lldb) po 0x00000001000021a0 LGPerson // 元類  (lldb) x/4gx 0x00000001000021a0 0x1000021a0: 0x00000001003340f0 0x00000001003340f0 0x1000021b0: 0x0000000101831790 0x0004e03100000007 (lldb) po 0x00000001003340f0 NSObject // 根元類  (lldb) x/4gx 0x00000001003340f0 // 根元類的isa指向本身 0x1003340f0: 0x00000001003340f0 0x0000000100334140 0x100334100: 0x0000000100640570 0x0005e03100000007 (lldb)  複製代碼

  • 那麼咱們再看下,繼承 LGPerson 的類 LGTercher,它的 isa 都指向了哪裏呢?
// Code
@interface LGTercher : LGPerson @end  @implementation LGTercher @end  // DeBug (lldb) x/4gx tercher // 實例對象的內存分佈 0x101c2c910: 0x001d800100002179 0x0000000000000000 0x101c2c920: 0x50626154534e5b2d 0x65695672656b6369 (lldb) p/x 0x001d800100002179 & 0x00007ffffffffff8ULL (unsigned long long) $12 = 0x0000000100002178 (lldb) po 0x0000000100002178 // 實例對象的isa指向了類LGTercher LGTercher  (lldb) x/4gx 0x0000000100002178 // 類LGTercher的內存分佈 0x100002178: 0x0000000100002150 0x00000001000021c8 0x100002188: 0x000000010032e430 0x0000801000000000 (lldb) po 0x0000000100002150 // 類的isa指向了它的元類 LGTercher  (lldb) x/4gx 0x0000000100002150 // 元類的內存分佈 0x100002150: 0x00000001003340f0 0x00000001000021a0 0x100002160: 0x0000000101c36480 0x0003e03100000007 (lldb) po 0x00000001003340f0 // 元類的isa指向了根元類 NSObject  (lldb) x/4gx 0x00000001003340f0 0x1003340f0: 0x00000001003340f0 0x0000000100334140 0x100334100: 0x0000000101c36870 0x0004e03100000007 (lldb)  複製代碼

由 LGTercher 的調試結果,咱們看到,元類 LGTercher 的 isa 直接指向了根元類,並不根據繼承關係指向 LGPerson.因此全部的元類都會直接指向根元類.app


經典的走向圖(繼承:實線,isa指向:虛線):函數

流程圖探究oop

  1. 全部的類都會指向它的元類,類歸屬於它的元類,包括根類 NSObject
  2. 全部的元類都直接指向了根元類,根元類也會指向本身
  3. 繼承關係存在於類與類,包括元類與元類之間,實例對象之間是沒有關係的
  4. 類的繼承關係,最終都繼承自 NSObject,包括根元類也繼承自根類,而根類繼承於 nil,無中生有

面試題:類在內存中存在幾份?ui

答案是一份.雖然類歸屬於元類,但類的信息在編譯後只會存在一份atom


類結構分析

以前利用Clang編譯獲得的main.cpp文件,咱們能夠看到,類編譯後的結果都是結構體,而每一個結構體都會"繼承"自NSObject_IMPL結構體,而Class類型表示了指向objc_class結構體的指針spa

struct NSObject_IMPL {
 Class isa; };  typedef struct objc_class *Class; 複製代碼

這時在cpp文件中,點不到objc_class結構體,咱們再來到781版本的objc源碼Source Browser全局搜一下。3d

在objc-runtime-new.h文件中的objc_class是最新的,而且它繼承自objc_object

  • objc_class:表示類在編譯後的結構體,其中第一個成員數據是繼承自父類的isa指針
  • superclass:表示當前類繼承的父類
  • cache:利用散列表來緩存調用過的方法,提交訪問的速度
  • bits:Class的核心信息

objc_object源碼:


bits類信息探索

由objc_class的結構體源碼,咱們能夠經過類的首地址偏移,來獲取到bits中的信息。

首地址

x/4gx LGPerson.class
0x100002250: 0x0000000100002228 0x0000000100334140 0x100002260: 0x000000010032e420 0x0000802400000000 複製代碼

LGPerson類的首地址爲0x100002250


偏移地址

  • ISAsuperclass都是objc_class的結構體指針,一共16字節。
  • cache中
// cache關鍵部分,static&方法()不在計算範圍內
struct cache_t { #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED  explicit_atomic<struct bucket_t *> _buckets;  explicit_atomic<mask_t> _mask; #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16  explicit_atomic<uintptr_t> _maskAndBuckets;  mask_t _mask_unused;  #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4  // _maskAndBuckets stores the mask shift in the low 4 bits, and  // the buckets pointer in the remainder of the value. The mask  // shift is the value where (0xffff >> shift) produces the correct  // mask. This is equal to 16 - log2(cache_size).  explicit_atomic<uintptr_t> _maskAndBuckets;  mask_t _mask_unused;   ...  #endif #if __LP64__  uint16_t _flags; #endif  uint16_t _occupied;   ... 複製代碼
  1. struct bucket_t *類型是結構體指針,8字節
  2. typedef uint32_t mask_t;類型是4字節
  3. typedef unsigned long uintptr_t;類型是8字節
  4. uint16_t類型是2字節

因此cache所佔內存一共12+2+2=16字節,最終的偏移量爲32字節


獲取bits內容

/* Debug  首地址爲:0x100002250 偏移量爲32字節,  class_data_bits_t bits的地址就在0x100002270 (16進制) */ p (class_data_bits_t *)0x100002270 // (class_data_bits_t *)$3 = 0x00000000100002270 p $3->data() // 調用bits中的data()方法 //(class_rw_t *)$4 = 0x0000000100b19bf0 p $4 /*  (class_rw_t) $6 = {  flags = 2148007936  witness = 0  ro_or_rw_ext = {  std::_ _1::atomic<unsigned long> = 4294975616  }  firstSubclass = LGTercher  nextSiblingClass = NSUUID  } */ 複製代碼

$6都是些啥玩意兒啊??!!

咱們點到bits.data()方法的返回類型class_rw_t

struct class_rw_t {
  ...   const method_array_t methods() const {  auto v = get_ro_or_rwe();  if (v.is<class_rw_ext_t *>()) {  return v.get<class_rw_ext_t *>()->methods;  } else {  return method_array_t{v.get<const class_ro_t *>()->baseMethods()};  }  }   const property_array_t properties() const {  auto v = get_ro_or_rwe();  if (v.is<class_rw_ext_t *>()) {  return v.get<class_rw_ext_t *>()->properties;  } else {  return property_array_t{v.get<const class_ro_t *>()->baseProperties};  }  } } 複製代碼

一直看到最後,看到了method_array_t methods()property_array_t properties()兩個方法。

  • 獲取properties

輸出後咱們看到,property_list_t中的屬性數據count有一條,屬性的名稱爲kc_name,和LGPerson中是相符的。


  • 獲取methods(從新Debug,$4和上邊$6同樣,都是class_rw_t數據)

此時咱們能夠看到,methods中一共有4個方法

(method_t) $9 = {
 name = "sayNB"  ... } (method_t) $10 = {  name = "kc_name"  ... } (method_t) $11 = {  name = "setKc_name:"  ... } (method_t) $12 = {  name = ".cxx_destruct"  ... } 複製代碼
  1. 類中添加的sayNB方法
  2. 屬性的set&get方法會在編譯過程當中自動生成
  3. oc封裝於C++底層,因此會默認添加destruct方法

總結:

  • 實例對象的isa指向類對象,類對象isa指向它的元類,元類isa指向根元類,根元類isa指向本身
  • 類最終都繼承了根類NSObject,根類繼承nil
  • 根類的isa指向根元類,根元類繼承自根類
  • 類的結構體包含isa、superclass、cache、bits成員數據
  • 類的屬性&實例函數都存在類結構體的class_data_bits_t bits

推薦參考

探究 cache_t (方法緩存)的本質

類 & 類結構分析

本文使用 mdnice 排版

相關文章
相關標籤/搜索