今天来探索一下类的成员方法和成员属性以及类方法都存放在哪里
Class的本质
首先先来补充一下类的本质Class,通过源码知道
typedef struct objc_class *Class;
Class 实际就是一个结构体指针
// 将结构体里面的方法,静态方法都去掉,简化如下struct 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
}
struct objc_object {
private:
isa_t isa
};
struct objc_class 继承了objc_object, 所以objc_class 也有isa指针,
接下来我们要研究的是bits这个属性,为什么要研究这个bits呢,还有怎么去拿到bits呢?
Bits的获取
为什么要研究这个bits呢?
还记得之前研究对象申请空间大小是看到过这样一段代码吗?这两个函数就是在struct objc_class 的成员函数,这样我们就可以顺藤摸瓜成员变量应该就在这个bits里面,方法可能也在里面
uint32_t unalignedInstanceSize() const {
ASSERT(isRealized());
return data()->ro()->instanceSize; // 这边返回的是对象所有成员变量的大小
}
class_rw_t *data() const {
return bits.data();
}
怎么去拿到bits呢?
首先我们想到的是通过指针平移来获取,如果平移的话我们就要先确定需要平移多少位,在bits前面有isa,superclass,cache,isa 8字节,superclass 8字节,cache?
我们来看看cache结构体
struct cache_t {
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
union {
struct {
explicit_atomic<mask_t> _maybeMask;
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache;
};
}
实际的cache_t结构体非常庞大,但是呢大部分都是方法和静态方法,所以我把他去掉只剩上面,由此可以看出cache_t 的大小只有16字节也就是我们需要平移32字节
接下来我们通过lldb来研究查看
@interface HFObject : NSObject
{
NSString *_a;
NSString *_b;
}
@property (nonatomic,strong) NSString *name;
@property (nonatomic,strong) NSString *nickName;
- (void)hello;
+ (void)world;
@end
HFObject *p = [HFObject alloc];
NSLog(@"%@",p);
(lldb) x/4gx HFObject.class
0x100004650: 0x0000000100004628 0x0000000100369140
0x100004660: 0x00000001003603c0 0x0000803000000000
打印HFObject的类地址,我们已知需要便宜32字节也就是0x20到bits位置
(lldb) p/x 0x100004650+32
(long) $2 = 0x0000000100004670
这样似乎看不出什么来
(lldb) p (class_data_bits_t *)0x0000000100004670
(class_data_bits_t *) $3 = 0x0000000100004670
这样就得到了class_data_bits_t 指针
(lldb) p $3->data()
(class_rw_t *) $4 = 0x0000000101034140
我们进入到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 *>(&ro_or_rw_ext)->methods;
} else {
return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->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 *>(&ro_or_rw_ext)->properties;
} else {
return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->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 *>(&ro_or_rw_ext)->protocols;
} else {
return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
}
}
methods会不会是方法列表呢?
properties 会不会是属性呢?
protocols 会不会是协议呢?
一个个来探索一下
(lldb) p $4->properties()
(const property_array_t) $8 = {
list_array_tt<property_t, property_list_t, RawPtr> = {
= {
list = {
ptr = 0x00000001000043c0
}
arrayAndFlag = 4294984640
}
}
}
(lldb) p $8.list.ptr
(property_list_t *const) $9 = 0x00000001000043c0
(lldb) p *$9
(property_list_t) $10 = {
entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 2)
}
(lldb) p $10.get(0)
(property_t) $11 = (name = "name", attributes = "T@\"NSString\",&,N,V_name")
(lldb) p $10.get(1)
(property_t) $12 = (name = "nickName", attributes = "T@\"NSString\",&,N,V_nickName")
这边我们发现property存放的是成员属性,但是成员变量不再里面
继续探究methods方法
(lldb) p $4->methods()
(const method_array_t) $5 = {
list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
= {
list = {
ptr = 0x00000001000042b8
}
arrayAndFlag = 4294984376
}
}
}
(lldb) p $5.list.ptr
(method_list_t *const) $6 = 0x00000001000042b8
(lldb) p *$6
(method_list_t) $7 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 5)
}
(lldb) p $7.get(0).big()
(method_t::big) $14 = {
name = "hello"
types = 0x0000000100003f7e "v16@0:8"
imp = 0x0000000100003c30 (KCObjcBuild`-[HFObject hello])
}
果然没错,methods存放的确实是成员方法,但是类方法却不再里面
之前我们计算对象大小时是用的data()->ro()->instanceSize;
接下来我们看看ro()
(lldb) p $4->ro()
(const class_ro_t *) $17 = 0x0000000100004270
(lldb) p $17->ivars
(const ivar_list_t *const) $18 = 0x0000000100004338
(lldb) p *$18
(const ivar_list_t) $19 = {
entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 4)
}
(lldb) p $19.get(0)
(ivar_t) $20 = {
offset = 0x00000001000045a8
name = 0x0000000100003f1d "_a"
type = 0x0000000100003f6a "@\"NSString\""
alignment_raw = 3
size = 8
}
果然ro里面存放着所有的成员变量
还有一个问题,类方法放在那里呢?,因为我们在这边methods里面并没有看到,对象的方法属性放在类里面,那类的方法是不是放在元类里面呢?
继续lldb看看
(lldb) x/4gx HFObject.class
0x100004650: 0x0000000100004628 0x0000000100369140
0x100004660: 0x00000001003603c0 0x0000803000000000
(lldb) p (class_data_bits_t *)0x0000000100004648
(class_data_bits_t *) $25 = 0x0000000100004648
(lldb) p $25->data()
(class_rw_t *) $26 = 0x0000000101034120
(lldb) p $26->methods()
(const method_array_t) $27 = {
list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
= {
list = {
ptr = 0x0000000100004250
}
arrayAndFlag = 4294984272
}
}
}
(lldb) p $27.list.ptr
(method_list_t *const) $28 = 0x0000000100004250
(lldb) p *$28
(method_list_t) $29 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 1)
}
(lldb) p $29.get(0).big()
(method_t::big) $30 = {
name = "world"
types = 0x0000000100003f7e "v16@0:8"
imp = 0x0000000100003c20 (KCObjcBuild`+[HFObject world])
}
果然在元类里面