前言:父类是子类的第一个成员变量,子类就拥有了父类的所有成员变量,叫结构体的伪继承
Class底层
Class是objc_class结构体指针,是objc_class的别名。
typedef struct objc_class *Class;
objc_class伪继承于objc_object,因此包含了isa,此外还有superclass、cache、bits几个成员变量,分析结构体内存,只需计算其成员变量大小。
struct objc_class : objc_object {
objc_class(const objc_class&) = delete;
objc_class(objc_class&&) = delete;
void operator=(const objc_class&) = delete;
void operator=(objc_class&&) = delete;
// 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
元类分析
首先,我们建立一个person
类,断点调试,通过lldb
得到p
的指针地址0x108cc2650
//mask 0x00007ffffffffff8
LGPerson *p = [LGPerson alloc];
(lldb) x p
0x108cc2650: 65 83 00 00 01 80 1d 01 00 00 00 00 00 00 00 00 e...............
0x108cc2660: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
然后lldb
命令x/4gx
得到它的类内存结构,第一个为isa
的地址0x011d800100008365
。通过OC本质探索一文,我们可以通过isa & mask
得到类地址0x0000000100008360
,po
得到了person
类
(lldb) x/4gx 0x108cc2650
0x108cc2650: 0x011d800100008365 0x0000000000000000
0x108cc2660: 0x0000000000000000 0x0000000000000000
(lldb) p/x 0x011d800100008365 & 0x00007ffffffffff8
(long) $13 = 0x0000000100008360
(lldb) po 0x0000000100008360
LGPerson
此时,我们再通过x/4gx
类地址0x0000000100008360
,又得到一个新的类内存结构,po
其isa & mask
竟然也得到了Person
类,其地址是0x0000000100008338
,这个类地址和上面的0x0000000100008360
哪一个是真正的person
类地址呢?
通过打印类地址,确定person
类,其地址为0x100008360
那另外一个地址是什么?我们可以通过MachOView
分析Mach-o
文件,首先确定了person
类地址为0x100008360
在符号表中,我们找到了内存地址为0x0000000100008338
的类,它是_objc_metaClass_LGPerson
类型,是一个元类,由person
类的isa
指向,元类是由系统编译时自动生成的。
根元类分析
继续上面的逻辑,p/x 元类isa & mask
,po 元类isa & mask
得到的了NSObject
类,所以person元类的isa指向了NSObject,这里NSObject的isa与superClass地址相同,都是0x00007fff8897eca0
(lldb) x/4gx 0x0000000100008338
0x100008338: 0x00007fff8897eca0 0x00007fff8897eca0
0x100008348: 0x0000000108cc2670 0x0002e03500000003
(lldb) p/x 0x00007fff8897eca0 & 0x00007ffffffffff8
(long) $28 = 0x00007fff8897eca0
(lldb) po 0x00007fff8897eca0
NSObject
重复操作上面逻辑,观察NSObject
类的isa
指向了自己,这时执行p/x NSObject.class
和x/4gx
,发现其isa地址同样为0x00007fff8897eca0
,所以这里NSObject为根元类
(lldb) x/4gx 0x00007fff8897eca0
0x7fff8897eca0: 0x00007fff8897eca0 0x00007fff8897ecc8
0x7fff8897ecb0: 0x0000000108b06f60 0x0005e03100000007
(lldb) p/x 0x00007fff8897eca0 & 0x00007ffffffffff8
(long) $30 = 0x00007fff8897eca0
(lldb) po 0x00007fff8897eca0
NSObject
(lldb) p/x NSObject.class
(Class) $32 = 0x00007fff8897ecc8 NSObject
(lldb) x/4gx 0x00007fff8897ecc8
0x7fff8897ecc8: 0x00007fff8897eca0 0x0000000000000000
0x7fff8897ecd8: 0x0000000108cbc610 0x0001801000000003
isa流程
superClass
通过以下代码打印,得到superClass的对应关系
void lgTestNSObject(void){
// NSObject实例对象
NSObject *object1 = [NSObject alloc];
// NSObject类
Class class = object_getClass(object1);
// NSObject元类
Class metaClass = object_getClass(class);
// NSObject根元类
Class rootMetaClass = object_getClass(metaClass);
// NSObject根根元类
Class rootRootMetaClass = object_getClass(rootMetaClass);
NSLog(@"\n%p 实例对象\n%p 类\n%p 元类\n%p 根元类\n%p 根根元类",object1,class,metaClass,rootMetaClass,rootRootMetaClass);
// LGPerson元类
Class pMetaClass = object_getClass(LGPerson.class);
Class psuperClass = class_getSuperclass(pMetaClass);
NSLog(@"%@ - %p",psuperClass,psuperClass);
// LGTeacher -> LGPerson -> NSObject
// 元类也有一条继承链
Class tMetaClass = object_getClass(LGTeacher.class);
Class tsuperClass = class_getSuperclass(tMetaClass);
NSLog(@"%@ - %p",tsuperClass,tsuperClass);
// NSObject 根类特殊情况
Class nsuperClass = class_getSuperclass(NSObject.class);
NSLog(@"%@ - %p",nsuperClass,nsuperClass);
// 根元类 -> NSObject
Class rnsuperClass = class_getSuperclass(metaClass);
NSLog(@"%@ - %p",rnsuperClass,rnsuperClass);
}
0x10893d4c0 实例对象
0x7fff8897ecc8 类
0x7fff8897eca0 元类
0x7fff8897eca0 根元类
0x7fff8897eca0 根根元类
NSObject - 0x7fff8897eca0
LGPerson - 0x100008338
(null) - 0x0
NSObject - 0x7fff8897ecc8
superClass流程
cache
cache_t
同样是结构体,其大小与变量有关
_bucketsAndMaybeMask
是uintptr_t
类型,就是unsignedlong
,大小为8字节union
为联合体,取内存较大成员变量_maybeMask
是mask_t(uint32_t)
类型,占4字节,_flags
为uint16_t
,占2字节,_occupied为uint16_t,占2字节- _originalPreoptCache为结构体指针,占8个字节
因此cache大小为16字节
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;
};
bits
bits
在底层是class_data_bits_t
结构体,而class_data_bits_t
关键就是返回class_rw_t
指针 ``` struct class_data_bits_t { friend objc_class;// Values are the FAST_ flags above. uintptr_t bits; private: …
public:
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
...
}
`class_rw_t`同样是一个结构体,其包含了`ro、method_array_t、property_array_t、protocol_array_t`方法调用<br />![1624002856890.jpg](https://cdn.nlark.com/yuque/0/2021/jpeg/21860440/1624002968995-b326f2fe-7852-4801-93a1-7e672094035d.jpeg#clientId=u6e1da652-1a75-4&from=drop&id=u96486672&margin=%5Bobject%20Object%5D&name=1624002856890.jpg&originHeight=1010&originWidth=1742&originalType=binary&ratio=1&size=210350&status=done&style=none&taskId=u57a82d04-89e8-45e2-8247-24a875ee243)<br />我们可以通过lldb调试验证下bits中包含的属性,在类结构体中(方法存储在方法区,不占用结构体内存)
- 第一成员变量`isa`,占8字节
- 第二成员变量`superClass`,占8字节
- 第三成员变量`cache`,占16字节
因此,第四成员变量`bits`地址,可以通过类地址 + 32得到
(lldb) p/x LGPerson.class (Class) $3 = 0x0000000100008380 LGPerson (lldb) p (class_data_bits_t)(0x0000000100008380 + 32) (class_data_bits_t) $4 = (bits = 4441863924)
得到class_data_bits_t后,可以依次取出其中的property,method,ivars
(lldb) p $4.data()
(class_rw_t ) $5 = 0x0000000108c176f0
(lldb) p ($5).properties()
(const property_array_t) $6 = {
list_array_tt
```
(lldb) p/x LGPerson.class
(Class) $0 = 0x0000000100008380 LGPerson
(lldb) p *(class_data_bits_t*)(0x0000000100008380 + 32)
(class_data_bits_t) $1 = (bits = 4443234692)
(lldb) p $1.data()
(class_rw_t *) $2 = 0x0000000108d66180
(lldb) p *$2
(class_rw_t) $3 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000344
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
(lldb) p $3.methods()
(const method_array_t) $4 = {
list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
= {
list = {
ptr = 0x0000000100008160
}
arrayAndFlag = 4295000416
}
}
}
(lldb) p $4.list.ptr
(method_list_t *const) $5 = 0x0000000100008160
(lldb) p *$5
(method_list_t) $6 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 6)
}
(lldb) p $6.get(0).big
(method_t::big) $7 = {
name = "hobby"
types = 0x0000000100003f53 "@16@0:8"
imp = 0x0000000100003dc0 (KCObjcBuild`-[LGPerson hobby])
}
Fix-it applied, fixed expression was:
$6.get(0).big()
(lldb) p $6.get(1).big
(method_t::big) $8 = {
name = "sayNB"
types = 0x0000000100003f5b "v16@0:8"
imp = 0x0000000100003d50 (KCObjcBuild`-[LGPerson sayNB])
}
Fix-it applied, fixed expression was:
$6.get(1).big()
(lldb) p $6.get(2).big
(method_t::big) $9 = {
name = "setHobby:"
types = 0x0000000100003f6f "v24@0:8@16"
imp = 0x0000000100003df0 (KCObjcBuild`-[LGPerson setHobby:])
}
Fix-it applied, fixed expression was:
$6.get(2).big()
(lldb) p $6.get(3).big
(method_t::big) $10 = {
name = "init"
types = 0x0000000100003f53 "@16@0:8"
imp = 0x0000000100003cf0 (KCObjcBuild`-[LGPerson init])
}
Fix-it applied, fixed expression was:
$6.get(3).big()
(lldb) p $6.get(4).big
(method_t::big) $11 = {
name = "name"
types = 0x0000000100003f53 "@16@0:8"
imp = 0x0000000100003d60 (KCObjcBuild`-[LGPerson name])
}
Fix-it applied, fixed expression was:
$6.get(4).big()
(lldb) p $6.get(5).big
(method_t::big) $12 = {
name = "setName:"
types = 0x0000000100003f6f "v24@0:8@16"
imp = 0x0000000100003d90 (KCObjcBuild`-[LGPerson setName:])
}
Fix-it applied, fixed expression was:
$6.get(5).big()
(lldb) p/x LGPerson.class
(Class) $0 = 0x0000000100008380 LGPerson
(lldb) p *(class_data_bits_t *)(0x0000000100008380 + 32)
(class_data_bits_t) $1 = (bits = 4441981524)
(lldb) p $1.data()
(class_rw_t *) $2 = 0x0000000108c34250
(lldb) p (*$2).ro()
(const class_ro_t *) $3 = 0x0000000100008118
(lldb) p (*$3).ivars
(const ivar_list_t *const) $4 = 0x00000001000081f8
(lldb) p *$4
(const ivar_list_t) $5 = {
entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 3)
}
(lldb) p $5.get(0)
(ivar_t) $6 = {
offset = 0x0000000100008318
name = 0x0000000100003f19 "subject"
type = 0x0000000100003f63 "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $5.get(1)
(ivar_t) $7 = {
offset = 0x0000000100008320
name = 0x0000000100003f21 "_name"
type = 0x0000000100003f63 "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $5.get(2)
(ivar_t) $8 = {
offset = 0x0000000100008328
name = 0x0000000100003f27 "_hobby"
type = 0x0000000100003f63 "@\"NSString\""
alignment_raw = 3
size = 8
}
总结
- class内存结构