objc底層:類的結構探索

Class結構總覽

Class -> objc_class -> objc_object {
    // Class ISA;  8字節
    Class superclass;//formerly cache pointer and vtable
    cache_t cache; // class_rw_t * plus custom rr/alloc flags  
    class_data_bits_t bits;
    class_rw_t *data() { 
        return bits.data();
    }
    void setData(class_rw_t *newData) {
       bits.setData(newData);
    }
    ... 後面就不一一列出
}
複製代碼

上圖能夠看出Class是一個objc_object 的結構體,內部的data()函數返回的是一個class_rw_t的結構體:c++

"... 省略咱們暫不關心的,具體代碼請前往相關工程探索"
struct class_rw_t {
    ...
    const class_ro_t *ro;
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    ...
}
複製代碼

看到class_rw_t內的結構能夠發現存儲有方法屬性等,並且有一個class_ro_t的結構體指針。git

struct class_ro_t {
    ...
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;
    ...
}
複製代碼

class_ro_t 裏邊竟然也有方法屬性等參數,其實class_ro_t就是保存類的最初始數據ro=readonly,而class_rw_t=readwrite,ro中的數據不容修改。github

接下來咱們經過一個項目經過objc源碼 objc源碼分析 查看 來看看究竟Class內部結構的這些屬性中到底存了哪些內容。objective-c

TestDemo

新建一個SSObject類繼承~NSObjectswift

#import <Foundation/Foundation.h>

@interface SSObject : NSObject
{
    NSString *_customValue;
}
@property (nonatomic, copy) NSString *cName;

- (void)sayHello;

+ (void)sayHappy;

@end
複製代碼
#import "SSObject.h"

@implementation SSObject

- (void)sayHello {
    
}

+ (void)sayHappy {
    
}
@end
複製代碼

main函數以下:bash

#import "SSObject.h"
#import <objc/runtime.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        SSObject *object = [[SSObject alloc] init];
        Class aClass = objc_getClass("SSObject");
        NSLog(@"~~~~~~~~~~~~~~~~%p",aClass);
    }
    return 0;
}
複製代碼

class_rw_t


運行項目咱們在NSLog處作斷點,經過lldb命令對內存進行打印:markdown

(lldb) x/2gx aClass // 打印aClass的內存結構
0x100001248: 0x001d800100001221 0x0000000100b00140

(lldb) po 0x100001248 // 起始地址打印的是類
SSObject

(lldb) 0x100001248 + 32字節 = 0x100001268 -> class_data_bits_t 
此處爲何 "+32",咱們再看objc_object的結構,
objc_object {
    // Class ISA;  8字節
    Class superclass; 8字節
    cache_t cache; 16字節
    class_data_bits_t bits;
} 能夠看出+32的位置正好就是 class_data_bits_t 的首地址。

因爲class_data_bits_t不是常規id等類型,咱們須要強轉獲得:
(lldb) p (class_data_bits_t *)0x100001268
(class_data_bits_t *) $2 = 0x0000000100001268

咱們想獲得class_rw_t的內容,即經過data()的函數
class_rw_t *data() { 
        return bits.data();
    }
(lldb) p $2->data()
(class_rw_t *) $3 = 0x0000000101039360

(lldb) p *$3 // 打印指針內容
(class_rw_t) $4 = {
  flags = 2148139008
  version = 0
  "ro = 0x00000001000011b0"
  methods = {
    ...
  }
  properties = {
    ...
  }
  protocols = {
    ...
  }
  firstSubclass = nil
  nextSiblingClass = NSDate
  demangledName = 0x0000000100000ef6 "SSObject"
}
$4 便是 "class_rw_t" 其內部的"ro" 便是"class_ro_t"
複製代碼

class_ro_t

(lldb) p $4.ro
(const class_ro_t *) $16 = 0x00000001000011b0
(lldb) p *$16
(const class_ro_t) $17 = {
  flags = 388
  instanceStart = 8
  instanceSize = 24
  reserved = 0
  ivarLayout = 0x0000000100000eff "\x02"
  name = 0x0000000100000ef6 "SSObject"
  "baseMethodList = 0x00000001000010e8"
  baseProtocols = 0x0000000000000000
  "ivars = 0x0000000100001150"
  weakIvarLayout = 0x0000000000000000 <no value available>
  baseProperties = 0x0000000100001198
  _swiftMetadataInitializer_NEVER_USE = {}
}
複製代碼

屬性 & 成員變量

咱們對class_rw_tclass_ro_t中的屬性和成員變量進行探索:app

(lldb) p $4.properties
(property_array_t) $12 = {
  list_array_tt<property_t, property_list_t> = {
     = {
      list = 0x0000000100001198
      arrayAndFlag = 4294971800
    }
  }
}
(lldb) p $12.list "打印全部屬性列表"
(property_list_t *) $13 = 0x0000000100001198
(lldb) p *$13
(property_list_t) $14 = {
  entsize_list_tt<property_t, property_list_t, 0> = {
    entsizeAndFlags = 16
    count = 1
    "屬性"
    first = (name = "cName", attributes = "T@"NSString",C,N,V_cName")
  }
}
複製代碼

經過結果咱們能夠看到屬性cName確實存放在class_rw_t中,可是咱們並無看到相似成員變量的屬性,咱們接着去看上述class_ro_t, 咱們看到相似ivars的字段,咱們看看裏邊究竟有哪些內容。函數

(lldb) p $17.ivars
(const ivar_list_t *const) $18 = 0x0000000100001150
(lldb) p *$18
(const ivar_list_t) $19 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0> = {
    entsizeAndFlags = 32
    "count = 2"
    first = {
      offset = 0x0000000100001218
      name = 0x0000000100000f31 "_customValue"
      type = 0x0000000100000f6b "@"NSString""
      alignment_raw = 3
      size = 8
    }
  }
}
複製代碼

Note:此處咱們確實看到咱們定義在頭文件中的NSString *_customValue; 可是咱們只定義了一個成員變量,爲什麼此處count=2,打印一下便可:oop

(lldb) p $19.get(1)
(ivar_t) $20 = {
  offset = 0x0000000100001210
  name = 0x0000000100000f3e "_cName"
  type = 0x0000000100000f6b "@"NSString""
  alignment_raw = 3
  size = 8
}
複製代碼

Why: 原來是咱們屬性cName自動生成的帶下劃線的成員變量,那麼咱們屬性和成員變量告一段落,接下來咱們看看方法。

實例方法

(lldb) p $17.baseMethodList
(method_list_t *const) $21 = 0x00000001000010e8
(lldb) p *$21
(method_list_t) $22 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 4
    first = {
      name = "sayHello"
      types = 0x0000000100000f50 "v16@0:8"
      imp = 0x0000000100000cf0 (ClassTest`-[SSObject sayHello] at SSObject.m:12)
    }
  }
}
複製代碼

誠然,咱們確實看到方法列表了,可是咱們貌似只定義了- (void)sayHello;+ (void)sayHappy 方法,爲什麼此處count=4,打印便知:

(lldb) p $22.get(1)
(method_t) $23 = {
  name = "cName"
  types = 0x0000000100000f58 "@16@0:8"
  imp = 0x0000000100000d10 (ClassTest`-[SSObject cName] at SSObject.h:16)
}
(lldb) p $22.get(2)
(method_t) $24 = {
  name = "setCName:"
  types = 0x0000000100000f60 "v24@0:8@16"
  imp = 0x0000000100000d40 (ClassTest`-[SSObject setCName:] at SSObject.h:16)
}
(lldb) p $22.get(3)
(method_t) $25 = {
  name = ".cxx_destruct"
  types = 0x0000000100000f50 "v16@0:8"
  imp = 0x0000000100000d80 (ClassTest`-[SSObject .cxx_destruct] at SSObject.m:10)
}
複製代碼

原來:咱們定義屬性系統會自動幫咱們生成setget方法,還有一個cxx_destruct的c++方法是系統默認添加的,這樣加上first裏邊的sayHello方法正好是4個,那麼問題來了,咱們的+ (void)sayHappy的方法呢?

類方法

以前咱們瞭解過isa的相關知識,類方法保存在meta-class中,此處暫時僅以上帝視角去驗證:

(lldb) p 0x001d800100001221 & 0x00007ffffffffff8
(long) $27 = 4294971936 "元類"
(lldb) x/2gx $27
0x100001220: 0x001d800100b000f1 0x0000000100b000f0
(lldb) p (class_data_bits_t *)0x100001240
(class_data_bits_t *) $28 = 0x0000000100001240
(lldb) p $28->data()
(class_rw_t *) $29 = 0x00000001010647b0
(lldb) p *$29
(class_rw_t) $30 = {
  flags = 2685075456
  version = 7
  ro = 0x00000001000010a0
  methods = {
   ...
  }
  properties = {
    ...
  }
  protocols = {
    ...
  }
  ...
}
(lldb) p $30.ro
(const class_ro_t *) $31 = 0x00000001000010a0
(lldb) p *$31
(const class_ro_t) $32 = {
  flags = 389
  instanceStart = 40
  instanceSize = 40
  reserved = 0
  ivarLayout = 0x0000000000000000 <no value available>
  name = 0x0000000100000ef6 "SSObject"
  baseMethodList = 0x0000000100001080
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000000000000
  weakIvarLayout = 0x0000000000000000 <no value available>
  baseProperties = 0x0000000000000000
  _swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $32.baseMethodList
(method_list_t *const) $33 = 0x0000000100001080
(lldb) p *$33
(method_list_t) $34 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 1
    first = {
      name = "sayHappy"
      types = 0x0000000100000f50 "v16@0:8"
      imp = 0x0000000100000d00 (ClassTest`+[SSObject sayHappy] at SSObject.m:16)
    }
  }
}
複製代碼

Okay如此咱們確實找到了+ (void)sayHappy方法所在。

總結

綜上所述,咱們分析並證明:

1.屬性會自動生成帶`_` 的成員變量。
2.若是咱們定義了屬性,系統會自動幫咱們生成set和get方法,並保存在類中。
3.靜態方法沒有保存在類中,而是元類中。
複製代碼

今天咱們就探討到這,像其中的protocol 等你們能夠繼續探索,有任何問題歡迎指正。

相關文章
相關標籤/搜索