OC底层原理09:isa走向&继承分析
本文的主要目的是分析 isa走向
和 继承
的分析
准备工作
定义两个类 :
- 继承自
NSObject
的ZJPerson
类
1 | @interface ZJPerson : NSObject |
- 继承自
ZJPerson
的ZJStudent
类
1 | @interface ZJStudent : ZJPerson |
- 在
main.m
中 分别创建两个对象:person & student
1 | int main(int argc, const char * argv[]) { |
元类
首先,我们先通过一个案例的 lldb
调试引入 元类
概念
在 main
中 NSLog(@"Hello World:%@ - %@",person,student);
处下一个 断点
,运行程序,开启 lldb
调试,调试的过程如下图所示:
x/4gx person
:查看person
的内存分布情况,拿到isa
的指针地址0x001d8001000021d1
p/x person
:拿到0x0000000101159370
地址,这个地址首先代表首地址
,其次代表当前对象
,也代表isa
,
那么为什么即代表当前对象,又代表 isa 呢?
任何自定义的类,如 ZJPerson
,均继承自 NSObject
,NSObject
默认第一个参数为 Class isa
,所以首地址会指向 isa
,isa
又代表当前的类
p/x 0x001d8001000021d1 & 0x00007ffffffffff8ULL
:获取类ZJPerson
的指针地址0x00000001000021d0
po 0x00000001000021d0
:根据类
的指针地址0x00000001000021d0
打印类信息
,得到
类ZJPerson
查看类的
内存分布
有三种方式:- 通过
类
的指针地址
:x 0x00000001000021d0
- 通过
class
的 API:x ZJPerson.class
- 通过
runtime
的 API:x object_getClass(person)
- 通过
x/4gx 0x00000001000021d0
:查看类ZJPerson
的内存分布
情况,拿到isa
指针地址0x00000001000021a8
po 0x00000001000021a8
:打印ZJPerson类
的isa
指针指向的内容,即元类
,其元类也是ZJPerson
p/x 0x00000001000021a8 & 0x00007ffffffffff8ULL
:通过类isa & mask
获取元类
的isa
指针地址0x00000001000021a8
po 0x00000001000021a8
:打印元类
的指针信息,拿到ZJPerson
根据调试过程,我们产生了一个疑问:为什么图中的 p/x 0x001d8001000021d1 & 0x00007ffffffffff8ULL
与 p/x 0x00000001000021a8 & 0x00007ffffffffff8ULL
中的类信息打印出来都是 ZJPerson
?
0x001d8001000021d1
是person对象的isa指针地址,其& mask
后得到的结果是获取对象person
的类ZJPerson
0x00000001000021a8
是元类的isa指针地址
,即ZJPerson类的类
的isa指针地址,在Apple中,我们简称ZJPerson类的类
为元类
所以,两个打印都是
ZJPerson
的根本原因就是因为元类
导致的
元类的说明:
下面来解释什么是元类,主要有以下几点说明:
我们知道
对象的isa
是指向类
,类
其实也是
一个对象
,可以称为类对象
,其isa
的位域指向
苹果定义的元类
元类
是系统生成
的,其定义和创建都是由编译器完成,在这个过程中,类
的归属来自
于元类
元类
是类对象
的类
,每个类都有一个独一无二的元类用来存储 类方法的相关信息。元类本身是没有名称的,由于与类相关联,所以使用了同类名一样的名称
isa走向
下面通过 lldb
命令来探索isa指针的走向,如下图所示:
由上图 lldb
探索可以得出一个关系链:对象 --> 类 --> 元类 --> NSobject --> 指向自己
下面用一张图表示 isa走向
流程:
由上面的分析结果我们可知:
实例对象
的isa
指针指向类
类对象
的isa
指针指向元
类元类
的isa
指针指向根元类
根元类
的isa
指针指向自己
NSObject到底有几个?
由上图可知,最后的根元类是 NSObject
,这和我们日常开发中所知道的 NSObject
是同一个吗?
有以下两种验证方式
- 【方法一】lldb命令验证
- 【方法二】代码验证
- 【方法一】lldb命令验证
从图中可以看出,由实例对象 person
最终 isa
找到的根源个 NSObject
,与下面开发中使用的 NSobject
相同,所以可以得出一个结论:内存中只存在一份根元类NSObject
,根元类的元类是指向它自己
- 【方法二】代码验证
通过三种不同的方式获取类,看他们打印的地址是否相同
1 | // MARK: -分析类对象在内存中存在几份 |
以上代码的运行结果是
1 | 2020-10-02 22:46:53.183944+0800 ZJObjc[21320:623345] |
从打印结果中可以看出,打印的 地址
都是 同一个
,所以 类对象
在内存中 只存在一份
。
【面试题】
问:类对象在内存中存在几份?
答:由于类信息在内存中只存在一份,所以 类对象只存在一份
继承
下面我们通过两道面试题来了解一下继承关系
1、【面试题1】:
我们知道 类ZJTeacher
继承自 类ZJPerson
,那么 实例对象tearher
和 实例对象person
有关系么?
答:没有关系
分析:
- 继承关系之来源与
类
- 实例对象没有关系
2、【面试题2】:
ZJTearher
继承自 ZJPerson
,ZJPerson
继承自 NSObject
,那么 NSObject
继承自谁呢?
答:NSObject
继承自 nil
用一张图来总结一下继承关系:
ZJTeacher 和 NSObject isa 走位图
由上面 继承
和 isa走位图
分析,我们可以得出 ZJTeacher
和 NSObject
的 isa走位
图:
继承 和 isa走位图
根据上面的探索以及各种验证,我们可以得到著名的 继承 & isa走位图
:
isa走位链三条:
teacher(实例对象) -> ZJTeacher(类) -> ZJTeacher(元类)-> NSObject(根元类)-> NSObject(根根元类)其实就是 自己
person(实例对象) -> ZJPerson(类) -> ZJPerson(元类)-> NSObject(根元类)-> NSObject(根根元类)其实就是 自己
object(实例对象) -> NSObject(类) -> NSObject(元类)-> NSObject(根元类)-> NSObject(根根元类)其实就是 自己
继承链两条:
ZJTeacher(子类)-> ZJPerson(父类)-> NSObject(根类) -> nil
ZJTeacher(子元类)-> ZJPerson(父元类)-> NSObject(根元类) -> NSObject(根类)-> nil
- Post title:OC底层原理09:isa走向&继承分析
- Post author:张建
- Create time:2020-09-22 21:57:52
- Post link:https://redefine.ohevan.com/2020/09/22/OC底层原理/OC底层原理09:isa走向&继承分析/
- Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.