在項目中,咱們常常建立類,而後經過類去 alloc
咱們所須要的對象,那咱們類的結構在底層是什麼樣的呢?咱們在類中所寫的屬性和方法又被 Xcode
編譯到哪裏去了呢?接下來針對這兩個問題來一探究竟。ios
Person
,並增長一些屬性、成員變量和方法,而後在 main 方法裏面 alloc
對象。int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [Person alloc];
NSLog(@"%@",person);
}
return 0;
}
複製代碼
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
複製代碼
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_object
和 objc_class
又是一個什麼鬼?它們兩個又有什麼關係?帶着這樣的疑問,咱們來到 Objc
源碼中一探究竟。緩存
咱們在源碼中全局搜索 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
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
裏面咱們對 methods
、properties
、protocols
都很熟悉,這不就是咱們要找的方法列表、屬性列表和協議列表嗎,可是會不會以爲有一絲絲的怪,這要怎麼去驗證就是咱們要找的呢?並且成員變量 ivars
哪裏去了呢?別急,接下來咱們經過 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
裏面只找到了 properties
和 methods
,那咱們的 ivars
哪去了呢?還有咱們的類方法又存到哪裏去了呢? 咱們回到 class_rw_t
結構體發現 const class_ro_t *ro
屬性,點進去一看,柳暗花明又一村啊!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;
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
屬性裏。
首先說兩個概念,知道這兩個概念以後,就好辦了。
(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
,以後的步驟就和上面同樣,此處就再也不演示了。
在 LLDB
調試打印屬性和方法的時候,會有 $6->first
和 $11->get(0)
的操做,那是由於 method_list_t
和 property_list_t
是繼承自 entsize_list_tt
,在這個結構體裏有 first
屬性和 Element& get(uint32_t i) const
方法。
- 經過
clang
找到類的本質是一個objc_class
結構體,而後再去objc
源碼中找到objc_class
結構體的底層源碼。- 經過分析
objc_class
結構體找出屬性、方法等原來是保存在bits->class_rw_t
的ro
屬性裏面,而後再經過LLDB
一步一步打印驗證出來。