在项目中,咱们常常建立类,而后经过类去 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
一步一步打印验证出来。