我们在上一节isa的结构分析分析了isa
的结构,我们在创建一个类的时候打印其地址得到的第一个地址就是它的isa地址,我们知道所有的类都有一个isa指向它的本质,那么除了isa
,类里面的结构还有什么内容呢?我们这一节就来分析类的结构
实例对象、类、元类分析
在探究类的结构之前我们先来分析一下实例对象,类和元类的关系
首先我们创建一个类如下:
#import <Foundation/Foundation.h>
#import "SYPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
SYPerson *person = [[SYPerson alloc]init];
NSLog(@"%p",person);
}
return 0;
}
注:
x/4gx: 以16进制形式打印地址内容,读取4个16字节内容
p/x: 打印变量的十六进制格式信
po: 打印变量的 description 方法
通过isa & ISA_MSAK可以查看 isa 指向的类信息
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
...
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
我们通过打印分析isa走位

分析:
- 实例对象
person
的isa指向了SYPerson
类 - 类
SYPerson
的isa指向了SYPerson
元类 - 元类
SYPerson
的isa指向了NSObject
类 - 类
NSObject
的isa指向了本身
那么得到的isa走位符合下图
isa流程图
查看类结构源码
要分析类SYPerson
我们知道它继承来自父类NSObject
,所以我们通过查看NSObject
的源码找到了:
@protocol NSObject
- (BOOL)isEqual:(id)object;
@property (readonly) NSUInteger hash;
@property (readonly) Class superclass;
这里我们可以看到NSObject
有一个Class
类型superclass
,再点进去发现typedef struct objc_class *Class;
说明Class是一个名为objc_class
的结构体,也就是说类的本质就是一个结构,那么我们想分析类结构实质上就是分析这个结构体,我们可以看到结构体objc_class
的源码发现有以下成员:
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
...
}
前面我们说过所有的类都有isa,为什么这里没有呢?那是因为objc_class
继承来自objc_object
,而objc_object
里面已经包含了isa
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
这里也说明所有以objc_object
为模板创建的类都有isa
。
通过地址平移分析objc_class
结构
- 地址平移
什么是地址平移呢?我们可以拿到类的地址后通过类的地址平移拿到里面的所有信息。通过下面例子我们可以了解一下地址平移
int a[4] = {1,2,3,4};
int *b = a;
NSLog(@"%p--%p--%p",&a,&a[0],&a[1]);
NSLog(@"%p--%p--%p",b,b+1,b+2);
for (int i = 0; i < 4; i++) {
int value = *(b + i);
NSLog(@"%d",value);
}
//打印结果
2020-09-13 15:54:53.066933+0800 KCObjc[61247:1111510] 0x7ffeefbff510--0x7ffeefbff510--0x7ffeefbff514
2020-09-13 15:54:53.067769+0800 KCObjc[61247:1111510] 0x7ffeefbff510--0x7ffeefbff514--0x7ffeefbff518
2020-09-13 15:54:53.067892+0800 KCObjc[61247:1111510] 1
2020-09-13 15:54:53.067976+0800 KCObjc[61247:1111510] 2
2020-09-13 15:54:53.068055+0800 KCObjc[61247:1111510] 3
2020-09-13 15:54:53.068130+0800 KCObjc[61247:1111510] 4
数组a的第一个元素的地址就是数组的地址,通过对数组的地址进行便宜依次得到了数组后续的值。
在知道什么是地址偏移后我们再通过地址偏移来分析objc_class
的结构。前面我们知道了结构体中的成员包含了:
-
Class ISA
继承自父类,占8字节 -
Class superclass
是一个指针,占用8个字节。 -
cache_t cache
以前缓存指针和虚函数表,占16字节 -
class_data_bits_t bits
结构体
那么我们类的信息是否存在于这里bits
呢?我们通过查看结构体源码发现
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
查看结构体class_rw_t
发现:
const method_array_t methods() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>()->methods;
} else {
return method_array_t{v.get<const class_ro_t *>()->baseMethods()};
}
}
const property_array_t properties() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>()->properties;
} else {
return property_array_t{v.get<const class_ro_t *>()->baseProperties};
}
}
const protocol_array_t protocols() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>()->protocols;
} else {
return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols};
}
}
这些代码是否看着很熟悉呢?有methods
、properties
、protocols
,我们可以进行验证这是否可以得到们类的信息。给我们的person
添加一个方法- (void)helloClass
;
前面我们分析类地址偏移可以得到类里面的所有信息,想查看bits
的信息我们需要将地址偏移8+8+18=32
字节.
(lldb) x/4gx SYPerson.class
0x1000021f0: 0x00000001000021c8 0x0000000100334140
0x100002200: 0x0000000101134640 0x0002801000000003
//0x1000021f0地址偏移32得到0x100002210
(lldb) p (class_data_bits_t *)0x100002210
(class_data_bits_t *) $1 = 0x0000000100002210
//调用data()
(lldb) p $1->data()
(class_rw_t *) $2 = 0x00000001011345e0
//获取data()方法得到的数据class_rw_t类型的$3
(lldb) p *$2
(class_rw_t) $3 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = 4294975744
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
//调用前面查看到方法如methods()
(lldb) p $3.methods()
(const method_array_t) $5 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002148
arrayAndFlag = 4294975816
}
}
}
//查看list里面数据
(lldb) p $5.list
(method_list_t *const) $6 = 0x0000000100002148
//获取到$6的内容,得到了我们申明的方法helloClass
(lldb) p *$6
(method_list_t) $7 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 1
first = {
name = "helloClass"
types = 0x0000000100000f9e "v16@0:8"
imp = 0x0000000100000f10 (KCObjc`-[SYPerson helloClass])
}
}
}
//我们添加属性后同理也可获得属性名称
总结
我们首先通过分析isa走位分析了实例对象、类、元类的关系,然后通过查看源码找到了了类的结构体objc_class
,明确了类的底层结构就是结构体,进而对结构体里面的成员进行了分析,发现类的属性方法都存储在class_data_bits_t bits
的结构体中,通过地址偏移也验证了这个结论。
网友评论