前言
- 通过上一篇 《OC底层系列四》-isa&superclass分析》中我们分析了isa和superclass的走向,知道了:
- OC类的isa(其位域成员shiftcls)存储着其元类的信息
- NSObject类的superclass指向nil,NSObject元类的superclass指向NSObject类。
- 今天对类的结构进行分析,探究类的属性、成员变量、实例方法、类方法在底层的实现。
目录
1、简介
本文主要从结合底层源码,结合lldb来分析探究类的属性、成员变量、实例方法、类方法在底层的实现。
2、类的结构分析
OC类的底层为objc_class的结构体,对类进行探索,即分析objc_class包含成员的和作用。
781源码中关于objc_class定义如下:
// objc-runtime-new.h
struct objc_class : objc_object {
// Class ISA; 继承自objc_object,包含isa
Class superclass; // 父类指针
cache_t cache; // formerly cache pointer and vtable,用于缓存指针和 vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
// class_rw_t 指针加上 rr/alloc 的标志
// bits 用于存储类的方法、属性、遵循的协议等信息的地方
// 返回一个 class_rw_t 类型的结构体变量
// Objective-C 类中的属性、方法还有遵循的协议等信息都保存在 class_rw_t 中
class_rw_t *data() const {
return bits.data();
}
...
}
我们要探究的类的属性、方法等都在bits里面,为了能够获取到bit的内存信息,我们需要计算出isa、superclass、cache占的大小,然后运用内存偏移获取到bits内存信息。
2.1、isa
isa继承自objc_object,因此第1个成员为isa,前面我们分析过其占8字节。
2.2、superclass
superclass为Class类型,即一个指向objc_class结构体的指针,占8字节。
// objc.h
typedef struct objc_class *Class;
2.3、cache
是一个cache_t的成员,cache_t定义关键代码如下如下:
struct cache_t {
// 非ARM设备
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic _buckets; // 结构体指针,8字节
explicit_atomic _mask; // mask_t即uint32_t类型,4字节
// ARM64位
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic _maskAndBuckets; // uintptr_t即unsigned long类型,8字节
mask_t _mask_unused; // mask_t即uint32_t类型,4字节
// 非ARM64位,即ARM32位
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
// _maskAndBuckets stores the mask shift in the low 4 bits, and
// the buckets pointer in the remainder of the value. The mask
// shift is the value where (0xffff >> shift) produces the correct
// mask. This is equal to 16 - log2(cache_size).
explicit_atomic _maskAndBuckets; // uintptr_t即unsigned long类型,8字节
mask_t _mask_unused; // mask_t即uint32_t类型,4字节
#else
#error Unknown cache mask storage type.
#endif
// uint16_t即unsigned short类型,占2字节
// 64位系统下
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
cache_t是一个结构体,缓存指针和函数表,由注释可以知道,无论是ARM64、ARM32还是模拟器环境下,其成员所占字节依次为8、4、2、2字节,根据内存对齐(参考带你深入理解iOS-内存对齐)可以知道,cache占16字节。
2.4、bits
- 综上我们可以知道,将类的首地址偏移32字节即可得到bits的内存地址信息。
- bits是一个class_data_bits_t类型的结构体指针,根据内存偏移可以得出class_data_bits_t的地址为:
- 方式一:通过**p/x Person.class **计算得出类首地址然后+32得出。
- 方式二:使用x/6gx 获取其类首地址开始6个8字节可以得出其内存地址
对Person和main做了修改,重新运行了项目,对应的内存地址发生了变换,但是不影响对bits的分析:
// Person.h
@interface Person : NSObject
{
NSString *_title;
}
@property(nonatomic,copy) NSString *name;
- (void)updateTitle:(NSString *)title;
@end
// Person.m
@implementation Person
- (void)updateTitle:(NSString *)title {
_title = title;
}
@end
// main.m
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
Person *p1 = [[Person alloc] init];
p1.name = @"jack";
[p1 updateTitle:@"Teacher"];
NSLog(@"Hello, World!,p1:%p",[p1 class]);
}
return 0;
}
使用方式二,log处添加断点,lldb验证如下:
// 打印Person首地址开始的6个8字节内存空间存储的值
(lldb) x/6gx Person.class
// 1-16字节
0x100008208: 0x00000001000081e0 0x0000000100333028
// 16-32字节
0x100008218: 0x0000000102028c10 0x0001802400000007
// 32-48字节,0x100008208即bits的内存地址,0x100008228即bits的内存地址
0x100008228: 0x0000000102028654 0x00000001000ac900
// 强转为class_data_bits_t指针
(lldb) p (class_data_bits_t *)0x100008208
(class_data_bits_t *) $1 = 0x0000000100008228
// 获取bits的data()
(lldb) p/x $1->data()
(class_rw_t *) $2 = 0x0000000101b04670
// 打印bits中的data信息
(lldb) p *$2
class_rw_t) $3 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic = {
Value = 4295000232
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
我们获取了objc_class的data()方法返回的内容,但是没有看到属性列表、方法等,data()返回一个class_rw_t的结构体指针,关键代码定义如下:
// objc-runtime-new.h
struct class_rw_t {
...
// 存储类的成员变量
const class_ro_t *ro() const {
auto v = get_ro_or_rwe();
if (slowpath(v.is())) {
return v.get()->ro;
}
return v.get();
}
// 存储类的实例方法
const method_array_t methods() const {
auto v = get_ro_or_rwe();
if (v.is()) {
return v.get()->methods;
} else {
return method_array_t{v.get()->baseMethods()};
}
}
// 存储类的属性
const property_array_t properties() const {
auto v = get_ro_or_rwe();
if (v.is()) {
return v.get()->properties;
} else {
return property_array_t{v.get()->baseProperties};
}
}
// 存储类的协议
const protocol_array_t protocols() const {
auto v = get_ro_or_rwe();
if (v.is()) {
return v.get()->protocols;
} else {
return protocol_array_t{v.get()->baseProtocols};
}
}
// 类方法存储在元类中
}
笔者已经把探索的结果写在了上面的注释里,下面通过lldb来进行验证。
2.4.1 属性
class_rw_t的properties() 方法返回一个property_array_t继承自list_array_tt的c++类,类的属性就存储在list中。
class list_array_tt {
...
private:
union {
List* list;
uintptr_t arrayAndFlag;
};
...
}
lldb验证:
(class_rw_t) $3 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic = {
Value = 4295000232
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
// 返回类中的属性列表信息,一个property_array_t类
(lldb) p $3.properties()
(const property_array_t) $4 = {
list_array_tt = {
= {
list = 0x00000001000081a0
arrayAndFlag = 4295000480
}
}
}
// 返回数组list的指针
(lldb) p $4.list
(property_list_t *const) $5 = 0x00000001000081a0
// 获取数组list存储的信息
(lldb) p *$5
(property_list_t) $6 = {
entsize_list_tt = {
entsizeAndFlags = 16
count = 1
first = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
}
}
// 获取第一个属性
(lldb) p $6.get(0)
(property_t) $7 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
// 获取第二个属性,越界,说明不存在
(lldb) p $6.get(1)
Assertion failed: (i < count), function get, file /Users/a002/PrivateReposity/github/xxerz/objc4/objc4-781/runtime/objc-runtime-new.h, line 438.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
- 通过上面的lldb打印结果,可以知道类的属性存储路径为:
Person类属性列表->objc_class.bits->class_rw_t.properties()->property_list_t.list->list,通过objc_class中的bits获取到类的属性列表。
2.4.2 成员变量
通过class_rw_t中的ro()方法获取,ro()返回一个class_ro_t结构体指针,其class_ro_t结构体中的ivars存储类的成员变量列表。
class_ro_t定义如下:
// objc-runtime-new.h
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;
验证ivars中存储着类的成员变量:
// 获取class_rw_t中的ro()返回值,返回一个结构体指针
lldb) p $3.ro()
(const class_ro_t *) $8 = 0x00000001000080a8
// 打印class_ro_t结构体的内容
(lldb) p *$8
(const class_ro_t) $9 = {
flags = 388
instanceStart = 8
instanceSize = 24
reserved = 0
ivarLayout = 0x0000000100003f7f "\x02"
name = 0x0000000100003f78 "Person"
baseMethodList = 0x00000001000080f0
baseProtocols = 0x0000000000000000
ivars = 0x0000000100008158
weakIvarLayout = 0x0000000000000000
baseProperties = 0x00000001000081a0
_swiftMetadataInitializer_NEVER_USE = {}
}
// 获取类的成员变量列表的指针
(lldb) p $9.ivars
(const ivar_list_t *const) $10 = 0x0000000100008158
(lldb) p *$10
// 获取成员变量列表
(const ivar_list_t) $11 = {
entsize_list_tt = {
entsizeAndFlags = 32
count = 2
first = {
offset = 0x00000001000081d0
name = 0x0000000100003f1a "_title"
type = 0x0000000100003f89 "@\"NSString\""
alignment_raw = 3
size = 8
}
}
}
// 获取第一个成员变量
(lldb) p $11.get(0)
(ivar_t) $12 = {
offset = 0x00000001000081d0
name = 0x0000000100003f1a "_title"
type = 0x0000000100003f89 "@\"NSString\""
alignment_raw = 3
size = 8
}
// 获取第二个成员变量
(lldb) p $11.get(1)
(ivar_t) $13 = {
offset = 0x00000001000081d8
name = 0x0000000100003f21 "_name"
type = 0x0000000100003f89 "@\"NSString\""
alignment_raw = 3
size = 8
}
- 通过p $9.ivars获取到了成员变量列表的指针。
- p *$10中获取成员变量列表,其存储在ivar_list_t结构体中,count=2说明有2个成员变量,依次打印为_title,_name
- 我们可以得到Person类的成员变量存储路径为:
Person类成员变量列表->objc_class.bits->class_rw_t.ro()->class_ro_t.ivars->ivars。
2.4.3 实例方法
class_rw_t的methods()返回该一个method_array_t类,其list成员中可以获取到类的实例方法。
验证过程如下:
// 获取class_rw_t中的methods返回值,返回一个method_array_t的类,继承自list_array_tt
(lldb) p $3.methods()
(const method_array_t) $14 = {
list_array_tt = {
= {
list = 0x00000001000080f0
arrayAndFlag = 4295000304
}
}
}
// 获取method_array_t中的list指针
(lldb) p $14.list
(method_list_t *const) $15 = 0x00000001000080f0
// 打印list内容,list即存储着类的实例方法信息
(lldb) p *$15
(method_list_t) $16 = {
entsize_list_tt = {
entsizeAndFlags = 26
// 有4个实例方法
count = 4
first = {
name = ".cxx_destruct"
types = 0x0000000100003f81 "v16@0:8"
imp = 0x0000000100003e30 (objc4-test`-[Person .cxx_destruct])
}
}
}
(lldb) p $16.get(0)
(method_t) $17 = {
name = ".cxx_destruct"
types = 0x0000000100003f81 "v16@0:8"
imp = 0x0000000100003e30 (objc4-test`-[Person .cxx_destruct])
}
(lldb) p $16.get(1)
(method_t) $18 = {
name = "name"
types = 0x0000000100003fa0 "@16@0:8"
imp = 0x0000000100003dd0 (objc4-test`-[Person name])
}
(lldb) p $16.get(2)
(method_t) $19 = {
name = "setName:"
types = 0x0000000100003f95 "v24@0:8@16"
imp = 0x0000000100003e00 (objc4-test`-[Person setName:])
}
(lldb) p $16.get(3)
(method_t) $20 = {
name = "updateTitle:"
types = 0x0000000100003f95 "v24@0:8@16"
imp = 0x0000000100003d80 (objc4-test`-[Person updateTitle:])
}
- 通过p *$15获取到了类的实例方法列表** method_list_t**。
- p *$15打印方法列表信息,由count=4得知有4个方法,依次为
-[Person .cxx_destruct]、-[Person . name]、-[Person . setName:]、-[Person .updateTitle:]。
2.4.4 类方法
- 类的实例方法存储在类中,那么类的类方法存储在哪里?
- 前面分析isa走位的时候提到了元类,类的isa指向元类。
类的实例的isa指向类,且类的实例方法存储在类的bits中。
我们可以推测类的类方法存储在元类的bits里。
验证如下:
// 获取类的指针
lldb) p/x Person.class
(Class) $0 = 0x0000000100008208 Person
// 获取类的内存信息
(lldb) x/4gx 0x0000000100008208
0x100008208: 0x00000001000081e0 0x0000000100333028
0x100008218: 0x0000000100693f70 0x0001802400000007
// 获取元类的指针
(lldb) p/x 0x00000001000081e0 & 0x00007ffffffffff8ULL
(unsigned long long) $1 = 0x00000001000081e0
// 获取元类的内存信息
(lldb) x/6gx 0x00000001000081e0
0x1000081e0: 0x0000000100333000 0x0000000100333000
0x1000081f0: 0x0000000100693ff0 0x0001e03500000007
// 0x100008200即元类的bits指针
0x100008200: 0x0000000100693e94 0x00000001000081e0
// 强转为class_data_bits_t *类型
(lldb) p (class_data_bits_t *)0x100008200
(class_data_bits_t *) $2 = 0x0000000100008200
// 获取元类的bits信息
(lldb) p $2->data()
(class_rw_t *) $3 = 0x0000000100693e90
// 打印元类的bits信息
(lldb) p *$3
(class_rw_t) $4 = {
flags = 2684878849
witness = 1
ro_or_rw_ext = {
std::__1::atomic = {
Value = 4295000128
}
}
firstSubclass = nil
nextSiblingClass = 0x00007fff900cdcd8
}
// 获取元类的示例方法列表,返回一个method_array_t类
(lldb) p $4.methods()
(const method_array_t) $5 = {
list_array_tt = {
= {
list = 0x0000000100008088
arrayAndFlag = 4295000200
}
}
}
// 获取method_array_t中list的内容,返回一个method_list_t结构体指针
(lldb) p $5.list
(method_list_t *const) $6 = 0x0000000100008088
// 打印list内容
(lldb) p *$6
(method_list_t) $7 = {
entsize_list_tt = {
entsizeAndFlags = 26
// 有一个实例方法
count = 1
first = {
name = "eat"
types = 0x0000000100003f81 "v16@0:8"
imp = 0x0000000100003d70 (objc4-test`+[Person eat])
}
}
}
(lldb) p $7.get(0)
(method_t) $8 = {
name = "eat"
types = 0x0000000100003f81 "v16@0:8"
imp = 0x0000000100003d70 (objc4-test`+[Person eat])
}
- 结果证明了类的类方法存储在其元类的bits里,Person元类中的示例方法只有一个,为+[Person eat]。
3、总结
- 本文使用lldb命令分析了类的属性、成员变量、实例方法、类方法在OC底层如何获取。
- 类的属性:通过@property定义的属性,存储在类的bits里,路径为
objc_class.bits->class_data_bits_t.data()->class_rw_t.properties()->property_array_t.list->list。 - 类的成员变量:通过{}定义的成员变量,存储在类的bits里,路径为
objc_class.bits->class_data_bits_t.data()->class_rw_t.ro()->class_ro_t.ivars->ivars。 - 类的实例方法:存储在类的bits里路径为
objc_class.bits->class_data_bits_t.data()->class_rw_t.methods()-> method_list_t.list->list。 - 类的类方法:存储在元类的bits里,路径为
该类的元类的objc_class.bits->class_data_bits_t.data()->class_rw_t.methods()-> method_list_t.list->list。