本页所使用的objc runtime 756.2,来自GITHUB
1.概念
1.1 类Class 的类型
Class在源码里的真正类型为objc_class的结构体。
先查看源码——在Xcode按下Shift+Command+O,选择objc-runtime-new.h,搜索objc_class,可以得知,Class底层编译实现的前4行如下:
可知类结构如下:
A. //Class ISA
继承自父类 objc_class 的 isa ,指向类的指针不需显示(内存占8位),这里它返回一个nonpointer的指针,相关源码如下:
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};B. Class superclass;
指向本类的父类(内存占8位)
C. cache_t cache;
存储指针和vtable,加速方法调用(内存占8位)
D. class_data_bits_t bits;
存储类的方法、属性、遵循的协议等信息的地方(内存占8位)
E. 其他方法/函数 – 不占内存
为何cache_t cache 这个变量占位位16,而不是8?
原因是因为这里的cache_t是结构体,其占内存为根据内部属性结构而定,为防止溢出,给予16位字节,而不是指针所需的8位。
Talk is cheap, show me the code, 伸伸手指,点开cache_t 的内部结构
我们来看看属性:
- bucket_t, 结构体,占8位字节
- mask_t,点开后具体实现为如下:
typedef uint32_t mask_t; |
为整型,占4个字节
- mask_t
typedef uint32_t mask_t; |
也是整形,占4个字节
共计8+4+4 = 16个字节
1.2 objc_class继承
在底层实现里,objc_class 继承自objc_object 的结构体
/// Represents an instance of a class. |
1.3 关于NSobject
万物皆对象 - Class 继承自NSObject
@interface NSObject <NSObject> { |
2. 类的属性与成员变量
属性的存储
上面提到了类对象的结构原理,我们知道了类结构里,类型位class_data_bits_t 的bits下面我们来探寻一下,类里面的属性上怎么保存的。
我们在代码里生成一个类,分别有成员变量hobby,以及属性nickName
代码如下:
@interface Person : NSObject{
NSString *hobby;
}
@property (nonatomic, copy) NSString *nickName;在主线代码引入:
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
Class pClass = object_getClass(person);
NSLog(@"%@ -- %p", person, pClass);
}
return 0;
}然后打印得到的类pClass,通过x/4gx 获取类结构如下:
(lldb) x/4gx pClass
0x1000023d0: 0x001d8001000023a9 0x0000000100b37140
0x1000023e0: 0x00000001003da280 0x0000000000000000观察结果得知0x1000023d0 位pClass在内存中的首地址,根据内存偏移原则,加上16+8+8共32位,即可得到bits的结构。
0x1000023d0,偏移32位,得到0x1000023f0。注意,这里要用p打印指针,而非打印值用的po
(lldb) p 0x1000023f0
4294976496oh NO!得到的是具体的值,这里需要强转一下:
(lldb) p (class_data_bits_t *)0x1000023f0
(class_data_bits_t *) $3 = 0x00000001000023f0得到$3 的值,并不是我们想要的结构,留意到objc_class里的方法 data() 方法:
struct objc_class : objc_object {
// Class ISA; // 8
Class superclass; // 8
cache_t cache; // 16 不是8 // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}data() 返回是class_rw_t,点进去看看,
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;留意到里面有 methods、properties、protocols等,原来这里等值便是类的相关属性方法等等结构。
现在对$3 执行data() 方法:
(lldb) p $3->data()
(class_rw_t *) $5 = 0x0000000000000000对得到的$5 进行值打印:
(lldb) p *$5
(class_rw_t) $5 = {
flags = 2148139008
version = 0
ro = 0x0000000100002308
methods = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002240
arrayAndFlag = 4294976064
}
}
}
properties = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x00000001000022f0
arrayAndFlag = 4294976240
}
}
}
protocols = {
list_array_tt<unsigned long, protocol_list_t> = {
= {
list = 0x0000000000000000
arrayAndFlag = 0
}
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
demangledName = 0x0000000000000000
}如上所示,$5 内结构一目了然,方法,属性,协议都显示了。
继续获取属性值:
(lldb) p $5.properties
(property_array_t) $7 = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x00000001000022f0
arrayAndFlag = 4294976240
}
}
}得到一个二维数组的$7,观察属性列表的类型为property_array_t,继承自list_array_tt,内部拥有
(lldb) p $7.list
(property_list_t *) $8 = 0x00000001000022f0再次对$8 进行* 取值,得到如下结果:
(lldb) p *$8
(property_list_t) $9 = {
entsize_list_tt<property_t, property_list_t, 0> = {
entsizeAndFlags = 16
count = 1
first = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
}
}从结果可以看出,我们的属性,就保存在properties 里中。
成员变量的存储
上面分析了属性的存储,但是我们没有看到成员变量 hobby,我们在properties 属性里,没有看到它,到底在哪里呢?下面我们继续查找。
既然不在properties 里,那尝试一下在$5 的 ro 里进行取值
(lldb) p $5.ro
(const class_ro_t *) $10 = 0x0000000100002308得到class_ro_t 类型的结构体,继续对他取值
(lldb) p *$10
(const class_ro_t) $11 = {
flags = 388
instanceStart = 8
instanceSize = 24
reserved = 0
ivarLayout = 0x0000000100001f8a "\x02"
name = 0x0000000100001f81 "LGPerson"
baseMethodList = 0x0000000100002240
baseProtocols = 0x0000000000000000
ivars = 0x00000001000022a8
weakIvarLayout = 0x0000000000000000
baseProperties = 0x00000001000022f0
}值这里可以看到ivars,就是我们需要的变量列表,打印一下:
(lldb) p $11.ivars
(const ivar_list_t *const) $12 = 0x00000001000022a8继续对 ivar_list_t 类型的$12 取值:
(lldb) p *$12
(const ivar_list_t) $13 = {
entsize_list_tt<ivar_t, ivar_list_t, 0> = {
entsizeAndFlags = 32
count = 2
first = {
offset = 0x0000000100002378
name = 0x0000000100001e64 "hobby"
type = 0x0000000100001fa7 "@\"NSString\""
alignment_raw = 3
size = 8
}
}
}
(lldb) p $.first
(const ivar_t) $13 = {
offset = 0x0000000100002378
name = 0x0000000100001e64 "hobby"
type = 0x0000000100001fa7 "@\"NSString\""
alignment_raw = 3
size = 8
}可以看到成员变量存在类的 ivars 属性里
3. 类的方法
3.1 类的实例方法
我们继续对类进行代码编辑,对Person
类添加实例方法和类方法
- (void)sayHello; // 实例方法 |
还是对类结构$5 进行解析,这次我们打印它的 methods 属性
(lldb) p $5.methods |
得到的是method_array_t 类型的$14, 是个数组类型,继续打印:
(lldb) p $14.list |
(lldb) p *$15 |
可见,实例方法sayHello
方法,存储在类的methods属性里
3.2 类的类方法存储
类方法在上述的过程中没有看到,那么它会在哪里呢?
为了找到它,我们重新回到最初的pClass
(lldb) x/4gx pClass |
得到isa值为0x001d800100002389,这时,唤出它的掩码 ISA_MASK 来获取它的元类
(lldb) p/x 0x001d800100002389 & 0x0000000ffffffff8 |
继续对元类**$1**的结构进行解析:
(lldb) x/4gx 0x0000000100002388 |
可知0x100002388是这个元类的地址,根据上文提到的内存便宜,我们继续加上2个16进制位,得到0x1000023a8,p一下:
(lldb) p (class_data_bits_t *)$2 |
继续执行上文实例方法类似的方案,执行data() — 取值 – 获取methods 的思路,具体代码如下:
(lldb) p $3->data() |
可见,类方法sayHappy
方法,存储在类的元类 methods属性里
4. 总结
- 类的本质上一个类型为objc_class的结构体,包含有isa、父类、属性、成员变量及方法列表等
- 类属性存在结构体的properties里
- 类的成员变量存在结构体的 ivars里
- 类的实例方法,存在结构体 的methods 属性里
- 类的类方法,存在父类的class_ro_t 里的methods里
如下图示: