類結構分析

開發中常常建立一個 TestClass.hTestClass.m 文件,而這個 TestClass 就是咱們所謂的類,那類的結構究竟是怎樣的呢?數組

新建一個類 TestClassbash

@interface TestClass : NSObject {
    NSString *flyIvar;
}

@property (nonatomic, strong) NSString *flyProperty;

@end

@implementation TestClass

@end
複製代碼

main.mmain 函數中寫入下方代碼:函數

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        TestClass *object = [[TestClass alloc] init];

        Class pClass = object_getClass(object);
        
        NSLog(@"%p - %p",object,pClass);
    }
    return 0;
}
複製代碼

一、OC 代碼編譯成 C++ 執行代碼

而後經過命令行工具將 main.m 轉換成 C++ 編譯的文件 main.cpp,命令以下: clang -rewrite-objc main.m -o main.cpp工具

打開 main.cpp 到最底部,就能看到 main 函數中的東西在 C++ 編譯成了以下代碼:學習

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        TestClass *object = ((TestClass *(*)(id, SEL))(void *)objc_msgSend)((id)((TestClass *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("TestClass"), sel_registerName("alloc")), sel_registerName("init"));

        Class pClass = object_getClass(object);

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_cj_20kzp4xj3x32g8_b79x0tg4h0000gn_T_main_508fc7_mi_0,object,pClass);
    }
    return 0;
}
複製代碼

二、Class 在底層源碼中的定義

查找到 Class 的定義爲:typedef struct objc_class *Class;,查看一下 objc_class 的結構體定義:ui

struct objc_class : objc_object {
    // 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_rw_t *data() { 
        return bits.data();
    }

    //...
}

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

複製代碼

上方代碼就能看到 objc_class 繼承於 objc_object,從這裏看到 OC 萬物之祖爲結構體類型,也就是編譯器在編譯的時候會把咱們的類編譯成結構體類型,而且在結構體中第一個爲 isa ,第二個爲當前類的父類,第三個爲 cache_t cache,第四個爲class_data_bits_t bitsatom

那麼問題來了,上方 TestClass 中的屬性 @property (nonatomic, strong) NSString *flyProperty; 和成員變量 NSString *flyIvar; 在哪兒存放着呢?spa

三、類中屬性變量和成員變量的查找

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;

    //...
}
複製代碼

通過一番查找在 bits.data(); 返回的 class_rw_t 裏的 class_ro_t *ro 中發現了咱們須要的屬性列表 property_list_t *baseProperties; ,那怎麼取到 class_data_bits_t bits 呢?命令行

一、類內存結構的分析

接下來看一下 TestClass 的內存結構: 3d

類的內存結構

從上方的內存結構中,能看到第二個匹配上了 superclass ,可是第三和第四個殊不知道是什麼。

對這個內存結構進行分析一下:

指向當前類的指針首地址爲 0x100001200,咱們知道第一個 0x001d8001000011d9isa 佔用 8 字節,因此咱們用 0x100001200 + 8 打印一下, 輸出了 <NSObject: 0x100001208>,而 superclassClass 類型的,Class 類型是一個 struct objc_class * 的結構體指針,因此它也佔用 8 字節。

接下來就到 0x100001210 了,嘗試將 0x100001210 強轉成 (cache_t *) 類型輸出一下,而後調用 cache_t 結構體裏面的 mask() 方法打印一下,發現輸出了 (mask_t) 類型的 $6,就肯定了 0x100001210 指針地址指向的爲 cache_t cache

image.png

二、cache_t 結構體佔用內存大小的計算

肯定了 cache_t cache 的指針首地址,還須要知道 cache_t cache 大小才能知道 class_data_bits_t bits 的首地址,此時就須要對 cache 的大小進行計算了。

struct cache_t {
    struct bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;

   //...
}
複製代碼

cache 是一個 cache_tstruct 結構體類型,結構體類型的大小計算須要計算結構體裏全部成員的大小。計算以下:

_bucketsstruct bucket_t * 的結構體指針類型佔用 8 字節_mask_occupied 都是 uint32_t 類型,uint32_t佔用 4 字節,方法不佔內存空間,則 cache 佔用內存爲 16 字節

三、class_data_bits_t 的分析

計算了 cache 的佔用內存,那麼 class_data_bits_t bits 的首地址就爲 0x100001210 + 16 = 0x100001220

接下來就要獲取 class_data_bits_t 中存儲的東西了, 強轉 0x100001220 類型爲 (class_data_bits_t *) ,獲取 bits -> data() 返回的 class_rw_t * 並打印顯示以下結果。

image.png

獲取 class_rw_t 中的 class_ro_t *ro並打印,baseProperties 中存儲的就是 屬性變量ivars 中存儲的是 成員變量

image.png

一、屬性變量和成員變量的獲取

分別打印 basePropertiesivars

  • baseProperties 是一個數組,數組中存儲了一個元素,就是 @property (nonatomic, strong) NSString *flyProperty;
  • ivars 一樣是一個數組,可是這個數組中存儲了兩個元素,第一個爲 NSString *flyIvar; ,第二個爲 NSString *_flyProperty;,由於屬性會自動生成一個帶下劃線的成員變量

baseProperties

ivars

這樣就找到屬性變量和成員變量存儲的地方。那麼方法列表呢?

二、實例方法和類方法列表的獲取

同理以獲取 baseProperties 的方式獲取 baseMethodList,第一個是 flyPropertygetter 方法,第二個是 flyPropertysetter 方法,第三個是編譯成 C++ 代碼系統添加的。

image.png

上方的打印是隻有一個屬性變量,沒有任何方法添加,如今添加一個類方法和一個實例方法再次打印看看。

//TestClass.h
@interface TestClass : NSObject {
    NSString *flyIvar;
}

@property (nonatomic, strong) NSString *flyProperty;

- (void)testClassInstanceMethod;
+ (void)testClassClassMethod;

@end

//TestClass.m
@implementation TestClass

- (void)testClassInstanceMethod {
    NSLog(@"testClassInstanceMethod");
}

+ (void)testClassClassMethod {
    NSLog(@"testClassClassMethod");
}
複製代碼

添加方法後的baseMethodList

在這裏發現少了一個方法 + (void)testClassClassMethod; ,那這個方法存儲在哪兒了?

咱們知道類裏面存儲的是實例對象的方法,元類裏面存儲的是類對象的方法,既然瞭解了這個就使用 isa 從元類裏面查找吧。

image.png

在推導的過程能夠看到對象的實例方法在類對象存儲,相對於元類,類方法又屬於元類的實例方法。

四、關於 NSObject 的內容補充

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
複製代碼

NSObject (Class)objc_class 的關係:NSObject (Class)objc_class 類型呢,在編譯的時候會編譯成 objc_class

NSObjectobjc_object 的關係:NSObject 是對 objc_object 結構體的仿寫,只是一個是咱們 OC 對象的,一個是編譯器編譯事後底層真正運行的。

五、關於 isa 的內容補充

這裏有一個小細節 ,爲何 struct objc_objectisa 的是 Class 類型的呢?咱們學習的過程明明知道 isa 是一個 isa_t 類型的聯合體,那這裏的 isa 怎麼不是 isa_t 類型的?

這是由於在早期的 iOS 調用 isa 就是爲了返回 Class 的,當咱們調用了 object_getClass() 時,OC 底層源碼調用的是 obj->getIsa();,接着進入 getIsa() ,又 return ISA() ,在 ISA() 中就能看到早期代碼使用 (Class)isa.bits; 返回,如今使用 (Class)(isa.bits & ISA_MASK); 返回,也就是說明了在返回時將二進制強轉成了一個 Class 類型的結構體,這也是 isaClass 類型的緣由。

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

inline Class 
objc_object::ISA() 
{
    assert(!isTaggedPointer()); 
#if SUPPORT_INDEXED_ISA
    if (isa.nonpointer) {
        uintptr_t slot = isa.indexcls;
        return classForIndex((unsigned)slot);
    }
    return (Class)isa.bits;
#else
    //這裏是返回類對象須要用 isa 的指針 & ISA_MASK
    return (Class)(isa.bits & ISA_MASK);
#endif
}

複製代碼

以上就是對類結構裏面屬性變量、成員變量的獲取和實例方法、類方法獲取的分析。

相關文章
相關標籤/搜索