在上一篇中我们通过Clang编译后的源代码研究了对象的本质,这篇我们进一步对类进行分析,还是从实际案例进行切入一探究竟。
类的分析
我们在对象alloc的时候打个断点,输入对应的lldb的调试指令,通过输出内存地址的形式对类进行分析,指令具体可参考下面
lldb调试指令
x/nuf <addr>
n 表示要显示的内存单元的个数
--------------------------
u 表示一个地址单元的长度
b 表示单字节
h 表示双字节
w 表示四字节
g 表示八字节
--------------------------
f 表示显示方式,可取如下值:
x 按十六进制格式显示变量
d 按十进制格式显示变量
u 按十进制格式显示无符号整型
o 按八进制格式显示变量
t 按二进制格式显示变量
a 按十六进制格式显示变量
i 指令地址格式
c 按字符格式显示变量
f 按浮点数格式显示变量
复制代码
断点调试
当断点在alloc时,输入lldb指令x/4gx p,可以看到对象输出的指针地址,首地址0x011d800100008489
为对象ATPerson的isa
指针地址,通过&
上ISA_MASK
(不同架构的ISA_MASK不同,本篇以x86_64架构为例,0x00007ffffffffff8ULL),得到结果为0x0000000100008488
,再通过po打印输出结果是ATPerson
类。
对以上的输出结果我们又有一个想法,如果获取类的isa指针并&
上ISA_MASK
,结果又是什么呢?
根据上面的输出,我们看到结果还是ATPerson
,和之前的是一样的,但细看2次的内存地址是不一致的,第一次的地址是0x0000000100008488
,而第二次的地址是0x0000000100008460
,都属于ATPerson
,但地址却不一致,我们再写个函数进行验证:
// 分析类对象内存存在个数
void atTestClassNum(void) {
Class class1 = [ATPerson class];
Class class2 = [ATPerson alloc].class;
Class class3 = object_getClass([ATPerson alloc]);
Class class4 = [ATPerson alloc].class;
NSLog(@"\n%p \n%p \n%p \n%p",class1,class2,class3,class4);
}
// 在main函数进行调用
int main(int argc, const char * argv[]) {
@autoreleasepool {
atTestClassNum();
}
return 0;
}
复制代码
打印输出
得的结果都是0x100008488
,也就是和上面第一次的结果一样,那第二次的ATPerson
到底是什么呢?根据编译后的可执行文件(Products->工程名
),借助MachOView工具对符号表进行分析,找到Symbol Table ~> Symbols
,搜索框输入class
,可以找到很多class相关的信息,我们定位到ATPerson
相关的位置,可以看到_OBJC_CLASS_$_ATPerson
,上面还有一个_OBJC_METACLASS_$_ATPerson
,这就是Objective-C元类的概念,元类是由系统自动编译生成的。
根据以上的调试分析,我们可以得出简单的结论:对象 isa
~> 类 isa
~> 元类
;下面通过2个方向深入了解类的形成。
一、isa走位
从上面的分析我们探索了对象 -> 类 -> 元类,那么有个疑问?元类的有没有isa?如果存在那元类的isa指向哪里?我们通过2个方向去探索接下来的问题:
自定义类
接着上面ATPerson继续往下分析
在1处输出元类ATPerson的地址0x0000000100008460
,继续执行x/4gx,拿到元类的isa为0x000000010036a0f0
,&
上ISA_MASK
得到了根元类0x000000010036a0f0
,也就是第2处的NSObject
,x/4gx再获取跟元类的isa(0x000000010036a0f0
),&
上ISA_MASK
后得到第三处的结果和第2处是一样的NSObject
;所以这条线是这样的:
对象 isa
~> 类 isa
~> 元类 isa
~> 根元类 isa
~> 根元类
。
NSObject类
打印NSObject根类的地址是0x000000010036a140
,获取NSObject的isa指针地址0x000000010036a0f0
,发现得到的根元类地址和上面的根元类地址一致。因此这条线是:
根类 isa
~> 根元类 isa
根据上面2种方向的探索,我们总结了下面这幅isa的走向图。
对象的isa找到类,类的isa找到元类,元类的isa找到根元类,根元类的isa找到它自己。
二、继承链
通过以下代码来分析类的继承关系,在main函数里执行并输出
说明:ATTeacher继承ATPerson,ATPerson继承NSObject
void atTestNSObject(void) {
// NSObject实例对象
NSObject *object = [NSObject alloc];
// NSObject类
Class class = object_getClass(object);
// 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 根根元类",object,class,metaClass,rootMetaClass,rootRootMetaClass);
// LGPerson元类
Class pMetaClass = object_getClass(ATPerson.class);
Class psuperClass = class_getSuperclass(pMetaClass);
NSLog(@"%@ - %p",psuperClass,psuperClass);
// LGTeacher -> LGPerson -> NSObject
// 元类也有一条继承链
Class tMetaClass = object_getClass(ATTeacher.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);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
atTestNSObject();
}
return 0;
}
复制代码
运行输出
从运行结果数据我们得出以下结论:
- NSObject的元类、根元类和根根元类是一样的
- ATPerson元类的父类是NSObject元类(
0x7fff8079dfe0
) - ATTeacher元类的父类是ATPerson元类
- NSObject类的父类是null
- NSObject元类的父类是NSObject类(
0x7fff8079e008
)
所以通过继承的分析可以得出下面这个关系图
结论
通过从2种不同的角度对Objective-C类的分析,isa走向和继承链的方式探索类的关系图,我们把2种情况合成一个图就是苹果官方文档所提供的类的走向图。
1、实例isa指向类,类的isa指向元类,元类的isa指向根元类,根元类的isa指向自己。
2、从继承的角度,子类指向父类,父类指向根类(NSObject),NSObject父类为nil;
子类元类指向父类元类,父类的元类指向根元类,根元类又指向NSObject类。
复制代码