iOS 手把手探索類的結構

1、前言

在項目中,咱們常常建立類,而後經過類去 alloc 咱們所須要的對象,那咱們類的結構在底層是什麼樣的呢?咱們在類中所寫的屬性和方法又被 Xcode 編譯到哪裏去了呢?接下來針對這兩個問題來一探究竟。ios

2、類的結構

  • 準備工做 首先咱們建立一個 Person,並增長一些屬性、成員變量和方法,而後在 main 方法裏面 alloc 對象。
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [Person alloc];
        NSLog(@"%@",person);
    }
    return 0;
}
複製代碼

Person對象

1.clang 編譯成 cpp 文件的幾種方式

// 經常使用方式
clang -rewrite-objc main.m -o main.cpp

// 存在UIKit等其餘動態引用庫時
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot/Application/Xcode.app/Comtents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.0.sdk main.m

// 模擬器
xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp

// 真機
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp

複製代碼

2.經過 cpp 文件查看類的結構

因爲 cpp 文件代碼太多,不方便查看,咱們能夠經過 command + F 搜索 Person,如下代碼是我精簡過的代碼:數組

typedef struct objc_object Person;
typedef struct objc_class *Class;
struct objc_object {
    Class _Nonnull isa __attribute__((deprecated));
};

__OBJC_RW_DLLIMPORT struct objc_class *objc_getClass(const char *);//獲取當前類
__OBJC_RW_DLLIMPORT struct objc_class *class_getSuperclass(struct objc_class *);//獲取superClass
__OBJC_RW_DLLIMPORT struct objc_class *objc_getMetaClass(const char *);//獲取元類
複製代碼

由此能夠得出咱們的 Person 在底層是一個 objc_object 結構體,結構體裏面有一個傳說中的 isa ,這個 isa 又是一個 objc_class 結構體指針。那這個 objc_objectobjc_class 又是一個什麼鬼?它們兩個又有什麼關係?帶着這樣的疑問,咱們來到 Objc 源碼中一探究竟。緩存

3、objc_class 的結構分析

1.struct objc_class 的源碼分析

咱們在源碼中全局搜索 struct objc_class,能獲得一些重要的信息bash

  • #if !__OBJC2__ 宏定義看出這個是老版本的 objc_class 結構,因此稍做了解一下,不作重點研究對象。
struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY; // isa 指針

#if !__OBJC2__
    Class _Nullable super_class   // 父類                           OBJC2_UNAVAILABLE;
    const char * _Nonnull name   // 類的名稱                         OBJC2_UNAVAILABLE;
    long version                // 版本                             OBJC2_UNAVAILABLE;
    long info                   // 信息                             OBJC2_UNAVAILABLE;
    long instance_size          // 對象大小                             OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars   // 成員變量列表               OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists    // 方法列表                   OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache     // 緩存                     OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols      // 協議列表     OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
複製代碼

接下來咱們來到重點 objc-runtime-new.h 文件中找到 objc_class 結構體,發現 objc_class 是繼承自 objc_object 的結構體,而 objc_object 裏面自帶一個 isa 指針,因此上面的一切的一切又是那麼的天然。app

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

struct objc_class : objc_object {
    // Class ISA;     // 隱藏的isa指針,佔 8 字節
    Class superclass; // 父類, 佔 8 字節
    cache_t cache;    // 緩存, 佔 16 字節 
    class_data_bits_t bits; // 類的字節數據,佔 8 字節
    
    class_rw_t *data() { 
        return bits.data();
    }
    
    /*** 此處省略若干行代碼  ***/
}
複製代碼

從上面的源碼中發現,咱們能夠 bits 這個屬性入手,而下面恰好又有經過 data() 方法獲取 bits 裏面的數據,返回值是 class_rw_t 類型,因此查看一下 class_rw_t 裏面都有什麼東西。iphone

2.class_rw_t 源碼分析

struct class_rw_t {
    uint32_t flags;
    uint32_t version;
    const class_ro_t *ro;
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;

    /*** 此處省略若干行代碼  ***/
}
複製代碼

class_rw_t 裏面咱們對 methodspropertiesprotocols 都很熟悉,這不就是咱們要找的方法列表、屬性列表和協議列表嗎,可是會不會以爲有一絲絲的怪,這要怎麼去驗證就是咱們要找的呢?並且成員變量 ivars 哪裏去了呢?別急,接下來咱們經過 LLDB 一步一步去驗證。源碼分析

3.LLDB 驗證

爲了節省你們的時間,下面驗證的代碼我會貼出來,你們能夠直接複製過去驗證,不過就是排版不是很好看。ui

(lldb) x/4gx pClass    //** 咱們經過 x/4gx pClass 指令將類的地址經過16進制打印出來
0x100002648: 0x001d800100002621 0x0000000100aff140
0x100002658: 0x00000001003a2290 0x0000000000000000

(lldb) po 0x0000000100aff140
NSObject
// po 0x0000000100aff140 獲得 NSObject,由此能夠驗證咱們從源碼中發現 objc_class 結構體
// 第一個是隱藏的 isa 指針,第二個是 superclass

(lldb) p/x 0x100002648 + 0x20     //** 經過地址偏移獲得 bits
(long) $1 = 0x0000000100002668
(lldb) p (class_data_bits_t *)0x0000000100002668 //** 將 bits 強轉 class_data_bits_t 類型
(class_data_bits_t *) $2 = 0x0000000100002668
(lldb) p $2->data()      //** 經過 bits 的 data() 方法獲得 class_rw_t
(class_rw_t *) $3 = 0x0000000102020850
(lldb) p *$3            //** 打印 class_rw_t 的內容
(class_rw_t) $4 = {
  flags = 2148139008
  version = 0
  ro = 0x0000000100002530
  methods = {
    list_array_tt<method_t, method_list_t> = {
       = {
        list = 0x0000000100002438
        arrayAndFlag = 4294976568
      }
    }
  }
  properties = {
    list_array_tt<property_t, property_list_t> = {
       = {
        list = 0x0000000100002518
        arrayAndFlag = 4294976792
      }
    }
  }
  protocols = {
    list_array_tt<unsigned long, protocol_list_t> = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  firstSubclass = nil
  nextSiblingClass = Person
  demangledName = 0x0000000000000000
}

(lldb) p $4.properties      //** 打印出 properties
(property_array_t) $5 = {
  list_array_tt<property_t, property_list_t> = {
     = {
      list = 0x0000000100002518
      arrayAndFlag = 4294976792
    }
  }
}
(lldb) p $5.list           //** 經過 list 打印出屬性
(property_list_t *) $6 = 0x0000000100002518
(lldb) p $6->first
(property_t) $9 = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")

(lldb) p $4.methods      //** 打印出 methods
(method_array_t) $10 = {
  list_array_tt<method_t, method_list_t> = {
     = {
      list = 0x0000000100002438
      arrayAndFlag = 4294976568
    }
  }
}
(lldb) p $10.list       //** 經過 methods 打印出方法
(method_list_t *) $11 = 0x0000000100002438
(lldb) p $11->get(0)
(method_t) $12 = {
  name = "sayHello"
  types = 0x0000000100001f8d "v16@0:8"
  imp = 0x0000000100001ad0 (LGTest`-[LGPerson sayHello] at LGPerson.m:13)
}
複製代碼

經過上面的 LLDB 調試,發現 class_rw_t 裏面只找到了 propertiesmethods,那咱們的 ivars 哪去了呢?還有咱們的類方法又存到哪裏去了呢? 咱們回到 class_rw_t 結構體發現 const class_ro_t *ro 屬性,點進去一看,柳暗花明又一村啊!spa

4.class_ro_t 源碼分析

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif
    const uint8_t * ivarLayout;
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;
    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};
複製代碼

class_ro_t 源碼中,貌似類的結構一目瞭然,話很少說,直接經過 LLDB 驗證。指針

(lldb) x/4gx pClass
0x1000025f0: 0x001d8001000025c9 0x0000000100aff140
0x100002600: 0x00000001003a2290 0x0000000000000000
(lldb) p/x 0x1000025f0 + 0x20 
(long) $1 = 0x0000000100002610
(lldb) p (class_data_bits_t *)0x0000000100002610 
(class_data_bits_t *) $2 = 0x0000000100002610
(lldb) p $2->data()
(class_rw_t *) $3 = 0x0000000101ebccf0
(lldb) p $3->ro             // 打印出 ro
(const class_ro_t *) $4 = 0x0000000100002388
(lldb) p *$4
(const class_ro_t) $5 = {
  flags = 388
  instanceStart = 8
  instanceSize = 24
  reserved = 0
  ivarLayout = 0x0000000100001f11 "\x02"
  name = 0x0000000100001f0a "Person"
  baseMethodList = 0x00000001000022c0
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000100002328
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x0000000100002370
}

(lldb) p $5.baseProperties      // 經過 ro 打印出 baseProperties 
(property_list_t *const) $6 = 0x0000000100002370
(lldb) p $6[0]                  // 經過 baseProperties 數組取出屬性
(property_list_t) $7 = {
  entsize_list_tt<property_t, property_list_t, 0> = {
    entsizeAndFlags = 16
    count = 1
    first = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
  }
}

(lldb) p $5.ivars               // 經過 ivars 取出成員變量
(const ivar_list_t *const) $11 = 0x0000000100002328
(lldb) p *$11
(const ivar_list_t) $12 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0> = {
    entsizeAndFlags = 32
    count = 2
    first = {
      offset = 0x00000001000025a8
      name = 0x0000000100001f52 "hobby"
      type = 0x0000000100001fa8 "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
  }
}

(lldb) p $5.baseMethodList     // 經過 baseMethodList 取出方法
(method_list_t *const) $13 = 0x00000001000022c0
(lldb) p *$13
(method_list_t) $14 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 4
    first = {
      name = "sayHello"
      types = 0x0000000100001f8d "v16@0:8"
      imp = 0x00000001000014b0 (LGTest`-[Person sayHello] at Person.m:12)
    }
  }
}
複製代碼

經過上述 LLDB 驗證得出,類的屬性、成員變量、方法、協議等都是存在了 objc_class 結構體裏面 bits 中的 rw 中的 ro 屬性裏。

5.如何找到類方法

首先說兩個概念,知道這兩個概念以後,就好辦了。

  • 對象方法是存在類裏面的
  • 類方法是存在元類裏面的
(lldb) x/4gx pClass         // 這裏打印的是類,因此第一個是類的 isa
0x1000025f0: 0x001d8001000025c9 0x0000000100aff140
0x100002600: 0x00000001003a2290 0x0000000000000000
(lldb) p/x 0x001d8001000025c9 & 0x00007ffffffffff8      // 經過類的 isa & 0x00007ffffffffff8 獲得元類地址
(long) $1 = 0x00000001000025c8        // po 元類地址獲得元類
(lldb) po 0x00000001000025c8
Person
複製代碼

拿到元類地址以後,經過地址 +0x20 的地址偏移獲得元類的 bits ,以後的步驟就和上面同樣,此處就再也不演示了。

6.補充

LLDB 調試打印屬性和方法的時候,會有 $6->first$11->get(0) 的操做,那是由於 method_list_tproperty_list_t 是繼承自 entsize_list_tt,在這個結構體裏有 first 屬性和 Element& get(uint32_t i) const 方法。

4、總結

  • 經過 clang 找到類的本質是一個 objc_class 結構體,而後再去 objc 源碼中找到 objc_class 結構體的底層源碼。
  • 經過分析 objc_class 結構體找出屬性、方法等原來是保存在 bits->class_rw_tro 屬性裏面,而後再經過 LLDB 一步一步打印驗證出來。
相關文章
相關標籤/搜索