iOS底层原理-类的探究分析(上)

上一篇中我们通过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类。

001.png
对以上的输出结果我们又有一个想法,如果获取类的isa指针并&ISA_MASK,结果又是什么呢?

002.png
根据上面的输出,我们看到结果还是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;
}
复制代码

打印输出

003.png
得的结果都是0x100008488,也就是和上面第一次的结果一样,那第二次的ATPerson到底是什么呢?根据编译后的可执行文件(Products->工程名),借助MachOView工具对符号表进行分析,找到Symbol Table ~> Symbols,搜索框输入class,可以找到很多class相关的信息,我们定位到ATPerson相关的位置,可以看到_OBJC_CLASS_$_ATPerson,上面还有一个_OBJC_METACLASS_$_ATPerson,这就是Objective-C元类的概念,元类是由系统自动编译生成的。

4.png

根据以上的调试分析,我们可以得出简单的结论:对象 isa ~> 类 isa ~> 元类;下面通过2个方向深入了解类的形成。

一、isa走位

从上面的分析我们探索了对象 -> 类 -> 元类,那么有个疑问?元类的有没有isa?如果存在那元类的isa指向哪里?我们通过2个方向去探索接下来的问题:

自定义类

接着上面ATPerson继续往下分析

6.png
在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类

7.png
打印NSObject根类的地址是0x000000010036a140,获取NSObject的isa指针地址0x000000010036a0f0,发现得到的根元类地址和上面的根元类地址一致。因此这条线是:
根类 isa ~> 根元类 isa

根据上面2种方向的探索,我们总结了下面这幅isa的走向图。

8.png
对象的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;
}
复制代码

运行输出

9.png
从运行结果数据我们得出以下结论:

  1. NSObject的元类、根元类和根根元类是一样的
  2. ATPerson元类的父类是NSObject元类(0x7fff8079dfe0)
  3. ATTeacher元类的父类是ATPerson元类
  4. NSObject类的父类是null
  5. NSObject元类的父类是NSObject类(0x7fff8079e008)

所以通过继承的分析可以得出下面这个关系图

10.png

结论

通过从2种不同的角度对Objective-C类的分析,isa走向和继承链的方式探索类的关系图,我们把2种情况合成一个图就是苹果官方文档所提供的类的走向图。

isa流程图.png

1、实例isa指向类,类的isa指向元类,元类的isa指向根元类,根元类的isa指向自己。
2、从继承的角度,子类指向父类,父类指向根类(NSObject),NSObject父类为nil;
  子类元类指向父类元类,父类的元类指向根元类,根元类又指向NSObject类。
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享