1.环境准备
版本为12.5的Xcode的编译器
配置可以参考这篇博客https://juejin.cn/post/6844903959161733133
2.问题抛出
2.1 现象
经过类对象的底层探索,我们知道
- 一个类对象有个isa指针,与上isa_mask就能得出类指针的地址。
- NSObject的底层实现是objc_object结构体。
- class的底层实现是objc_class 结构体指针。
实际上,根据源码,我们还知道objc_class继承了objc_object,故类里面也有isa指针。我们可以通过打印类指针得到类里面的isa指针。
我们将类里的isa指针与上isa_mask,又发现了一个TWPerson类。
2.2 分析与问题
为什么呢?难道内存里有好多个不同地址的TWPerson类?
类对象里的isa和类里的isa指向的不同地址的TWPerson类是同一个类么?
猜想:内存中会创建不止一个TWPerson类?
今天就让我们好好探究一下类。
3.METACLASS元类
3.1 元类的发现
那我们多创建几个TWPerson类对象,打印出他们的类的地址。
//MARK: - 分析类对象内存存在个数
void TestClassNum(void){
Class class1 = [TWPerson class];
Class class2 = [TWPerson alloc].class;
Class class3 = object_getClass([TWPerson alloc]);
Class class4 = [TWPerson alloc].class;
NSLog(@"\n%p\n%p\n%p\n%p",class1,class2,class3,class4);
}
故之前的应该不是TWPerson类。
我们可以通过MachOView工具查看程序运行的内存,搜索关键词TWPerson。
除了_OBJC_CLASS_$_TWPerson还有一个_OBJC_METACLASS_$_TWPerson。
我们称之为元类。
3.2 isa的走位图
我们打印元类内存,并继续用元类的isa指针与上isa_mask,看看会发生什么:
发现元类的isa指针能得到NSObject,这个NSObject是元类NSObject还是NSObject类呢?
// 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 NSObject实例对象\n%@ %p NSObject类\n%@ %p META NSObjcet元类\n%@ %p ROOT META NSObject根元类\n%@ %p ROOT ROOT META NSObject根根元类",object1,object1,class,class,metaClass,metaClass,rootMetaClass,rootMetaClass,rootRootMetaClass,rootRootMetaClass);
这下就清楚了,TWPerson的元类的isa的类指针是指向NSObject的元类,而不是NSObject类。
3.2 元类的继承链
// TWPerson元类
Class pMetaClass = object_getClass(TWPerson.class);
Class psuperClass = class_getSuperclass(pMetaClass);
NSLog(@"\nMETA TWPerson:%@ %p\nSUPER META TWPerson父类:%@ %p",pMetaClass,pMetaClass,psuperClass,psuperClass);
TWPerson的元类的父类是NSObject的元类。
我们新建一个TWTeacher类,作为TWPerson的子类。
// TWTeacher -> TWPerson -> NSObject
// 元类也有一条继承链
Class tMetaClass = object_getClass(TWTeacher.class);
Class tsuperClass = class_getSuperclass(tMetaClass);
NSLog(@"META TWTeacher %@ %p",tMetaClass,tMetaClass);
NSLog(@"SUPER META TWTeacher %@ %p",tsuperClass,tsuperClass);
// NSObject 类的superclass
Class nsuperClass = class_getSuperclass(NSObject.class);
NSLog(@"SUPER NSObject: %@ %p",nsuperClass,nsuperClass);
// 元类的superclass -> NSObject
Class rnsuperClass = class_getSuperclass(metaClass);
NSLog(@"SUPER META NSObject: %@ %p",rnsuperClass,rnsuperClass);
到这里,我们可以画出一张类的继承图。
3.3 总结 isa链&继承链 苹果官方图:
4.类的信息存储探索
4.1 内存偏移
在探索类里面的信息存储之前,我们先通过一张图简单了解一下什么是内存偏移:
4.2 objc_class结构体
第一个是isa。 第二个是父类,验证一下:
4.3 bits探索
4.3.1 bits的偏移量计算
我们先探究bits,要探究bits 需要知道isa superclass cache的大小,知道得到bits的偏移量。Isa和super class是8+8=16字节。
- staic存在数据段中,不在结构体内存中。
- 方法存在代码段中,不在结构体内存中。
故只要看cache_t里的这两个属性。
8字节
4字节
_flags 2字节 _occupied 2字节
故结构体就是8字节
下面的是个指针,故也是8字节。
由于是共用体,占据最大属性的内存大小,故共用体的内存大小就是8字节。
故cache_t的总大小是8+8=16字节。
故偏移量=16+16=32=0x20
这样我们就知道bits在内存中的位置了。
4.3.2 类中的数据获取
class_rw_t *data() const {
return bits.data();
}
struct class_rw_t {
uint32_t flags;
uint16_t witness;
#if SUPPORT_INDEXED_ISA
uint16_t index;
#endif
explicit_atomic<uintptr_t> ro_or_rw_ext;
Class firstSubclass;
Class nextSiblingClass;
private:
//...
public:
//...
const class_ro_t *ro() const {
auto v = get_ro_or_rwe();
if (slowpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
}
return v.get<const class_ro_t *>(&ro_or_rw_ext);
}
void set_ro(const class_ro_t *ro) {
//...
}
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};
}
}
};
可以通过class_rw_t指针获取到类里的信息。
4.3.3 类的属性
class property_array_t :
public list_array_tt<property_t, property_list_t, RawPtr>
{
typedef list_array_tt<property_t, property_list_t, RawPtr> Super;
public:
property_array_t() : Super() { }
property_array_t(property_list_t *l) : Super(l) { }
};
给TWPerson类加个属性 name/成员变量 obj/对象方法 sayhi/类方法 saybye,我们再去找找。
我们依然可以在properties中找到name,但是找不到obj。
4.3.4 类的对象方法
在class_rw_t指针,我们还有个methods。里面应该存了方法,发现读取不到。
通过阅读源码,可以发现,原来方法信息都存在了结构体big中。难怪我们之前没有查看到。
通过big,我们就能看到方法了。但是发现其中并没有类方法saybye。
(lldb) p $15.get(0).big()
(method_t::big) $26 = {
name = "sayhi"
types = 0x0000000100003f63 "v16@0:8"
imp = 0x0000000100003b40 (TWbit`-[TWPerson sayhi] at TWPerson.m:12)
}
(lldb) p $15.get(1).big()
(method_t::big) $27 = {
name = "array1"
types = 0x0000000100003f6b "@16@0:8"
imp = 0x0000000100003ba0 (TWbit`-[TWPerson array1] at TWPerson.h:17)
}
(lldb) p $15.get(2).big()
(method_t::big) $28 = {
name = "setArray1:"
types = 0x0000000100003f73 "v24@0:8@16"
imp = 0x0000000100003bc0 (TWbit`-[TWPerson setArray1:] at TWPerson.h:17)
}
(lldb) p $15.get(3).big()
(method_t::big) $29 = {
name = "setMArray:"
types = 0x0000000100003f73 "v24@0:8@16"
imp = 0x0000000100003c80 (TWbit`-[TWPerson setMArray:] at TWPerson.h:19)
}
(lldb) p $15.get(4).big()
(method_t::big) $30 = {
name = "name"
types = 0x0000000100003f6b "@16@0:8"
imp = 0x0000000100003cc0 (TWbit`-[TWPerson name] at TWPerson.h:20)
}
(lldb) p $15.get(5).big()
(method_t::big) $31 = {
name = "setArray:"
types = 0x0000000100003f73 "v24@0:8@16"
imp = 0x0000000100003c20 (TWbit`-[TWPerson setArray:] at TWPerson.h:18)
}
(lldb) p $15.get(6).big()
(method_t::big) $32 = {
name = ".cxx_destruct"
types = 0x0000000100003f63 "v16@0:8"
imp = 0x0000000100003d30 (TWbit`-[TWPerson .cxx_destruct] at TWPerson.m:11)
}
(lldb) p $15.get(7).big()
(method_t::big) $33 = {
name = "array"
types = 0x0000000100003f6b "@16@0:8"
imp = 0x0000000100003c00 (TWbit`-[TWPerson array] at TWPerson.h:18)
}
(lldb) p $15.get(8).big()
(method_t::big) $34 = {
name = "setName:"
types = 0x0000000100003f73 "v24@0:8@16"
imp = 0x0000000100003cf0 (TWbit`-[TWPerson setName:] at TWPerson.h:20)
}
(lldb) p $15.get(9).big()
(method_t::big) $35 = {
name = "mArray"
types = 0x0000000100003f6b "@16@0:8"
imp = 0x0000000100003c60 (TWbit`-[TWPerson mArray] at TWPerson.h:19)
}
4.3.5 类的成员变量
那我们试试class_rw_t指针中的ro方法,会返回一个class_ro_t的指针。打印它的内容。
好家伙,之前的method和properties都在里面。猜测ivars 里应该存的是成员变量吧,验证一下。
(lldb) p $39.ivars
(const ivar_list_t *const) $40 = 0x0000000100008228
(lldb) p *$40
(const ivar_list_t) $41 = {
entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 5)
}
(lldb) p $41.get(0)
(ivar_t) $42 = {
offset = 0x0000000100008370
name = 0x0000000100003eb2 "obj"
type = 0x0000000100003f7e "@\"NSObject\""
alignment_raw = 3
size = 8
}
(lldb) p $41.get(1)
(ivar_t) $43 = {
offset = 0x0000000100008378
name = 0x0000000100003eb6 "_array1"
type = 0x0000000100003f8a "@\"NSArray\""
alignment_raw = 3
size = 8
}
(lldb) p $41.get(2)
(ivar_t) $44 = {
offset = 0x0000000100008380
name = 0x0000000100003ebe "_array"
type = 0x0000000100003f8a "@\"NSArray\""
alignment_raw = 3
size = 8
}
(lldb) p $41.get(3)
(ivar_t) $45 = {
offset = 0x0000000100008388
name = 0x0000000100003ec5 "_mArray"
type = 0x0000000100003f95 "@\"NSMutableArray\""
alignment_raw = 3
size = 8
}
(lldb) p $41.get(4)
(ivar_t) $46 = {
offset = 0x0000000100008390
name = 0x0000000100003ecd "_name"
type = 0x0000000100003fa7 "@\"NSString\""
alignment_raw = 3
size = 8
}
果然,成员变量都存在了ivar中,在这里,我们找到了之前没找到的成员变量obj。
4.3.6 类的类方法(存在了元类中)
现在就差一个类方法不知道存在哪里了。而我们在之前打印出的ro指针中,其实发现TWPerson.class中的内容已经探索的差不多了。你在里面死命找也不会找到TWPerson的类方法的。(因为我已经找过了)
突然,我们想起了之前发现的一个还不知道作用的元类,我们可以有个猜想,类方法可能存在了TWPerson的元类中。
先找到元类的地址:
打印出元类的内容,和之前探索TWPerson类一样。
终于找到了类方法 saybye!
5.结尾
也到了我们say bye的时候,到这里,我们把TWPerson类的成员变量、属性、对象方法、类方法都在内存中找到了!
如有问题,欢迎大家留言,与我交流。