「iOS」————继承链与对象的结构
iOS学习
- 前言
- 对象的底层结构
- isa的类型isa_t
- objc_class & objc_object
- 类信息的静态与动态存储(ro、rw、rwe机制)
- cache
- bits
- 继承链
- isKindOfClass和isMemberOfClass
- `isKindOfClass:`
- `isMemberofClass`
前言
对 对象底层结构的相关信息有点遗忘,简略记录一下。顺便记录一下继承链相关。
对象的底层结构
isa指针采用了典型的适配器设计模式。
适配器设计模式(即将底层接口适配为客户端需要的接口)
,达成上下层接口隔离的目的。
- 上层OC对象 只需要通过 isa、Class、objc_object、objc_class 等接口访问对象和类信息。 底层实现
- 通过联合体、结构体、位域等方式,封装了复杂的内存布局和数据结构。
- 适配器(如isa_t、objc_object、objc_class、cache_t、class_rw_t等)把底层复杂的数据结构和操作,适配成了上层易用的接口,实现了“接口隔离”和“解耦”。
isa的类型isa_t
union isa_t { //联合体isa_t() { }isa_t(uintptr_t value) : bits(value) { }//提供了cls 和 bits ,两者是互斥关系,即通过cls初始化,bits无默认值,通过bits初始化,cls无默认值Class cls;uintptr_t bits;
#if defined(ISA_BITFIELD)struct {ISA_BITFIELD; // defined in isa.h 结构体定义的位域,储存类信息和其他信息。};
#endif
};
位域如下:
nonpointer
有两个值,表示自定义的类等,占1
位0
:纯isa指针
1
:不只是类对象地址
,isa中包含了类信息
、对象的引用计数
等
has_assoc
表示关联对象标志
位,占1
位0
:没有关联
对象1
:存在关联
对象
has_cxx_dtor
表示该对象是否有C++/OC的析构器
(类似于dealloc
),占1
位- 如果
有
析构函数,则需要做析构
逻辑 - 如果
没有
,则可以更快的释放
对象
- 如果
shiftcls
表示存储类的指针的值
(类的地址), 即类信息arm64
中占33
位,开启指针优化的情况下,在arm64架构中有33
位用来存储类指针x86_64
中占44
位
magic
用于调试器判断当前对象是真的对象
还是没有初始化的空间
,占6
位weakly_refrenced
是 指对象是否被指向
或者曾经指向一个ARC的弱变量
- 没有弱引用的对象可以更快释放
deallocating
标志对象是是否正在释放
内存has_sidetable_rc
表示 当对象引用计数大于10
时,则需要借用该变量存储进位
extra_rc
(额外的引用计数) ,表示该对象的引用计数值
,实际上是引用计数值减1- 如果对象的
引用计数为10
,那么extra_rc
为9(这个仅为举例说明),实际上iPhone 真机上的extra_rc
是使用 19位来存储引用计数的
- 如果对象的
cls
与 isa
关联原理
就是isa
指针中的shiftcls
位域中存储了类信息
,其中initInstanceIsa
的过程是将 calloc
指针 和当前的 类cls
关联起来。
objc_class & objc_object
所有Class都是以objc_class为模版继承而来的。isa指针的类型是Class,由objc_class
定义的类型。
而结构体类型objc_class继承自objc_object。obje_object是一个结构体,且有一个isa属性,因此objc_class也拥有isa属性
NSObject
中的isa
在底层是由Class
定义的,其中class
的底层编码来自 objc_class
类型,所以NSObject
也拥有了isa
属性
总结
- 所有的
对象
+类
+元类
都有isa
属性 - 所有的
对象
都是由objc_object
继承来的,类继承自objc_class - 简单概括就是
万物皆对象
,万物皆来源于objc_object
,有以下两点结论:- 所有以
objc_object
为模板 创建的对象
,都有isa
属性 - 所有以
objc_class
为模板,创建的类
,都有isa
属性
- 所有以
- 在结构层面可以通俗的理解为
上层OC
与底层
的对接
:下层
是通过结构体
定义的模板
,例如objc_class、objc_object
上层
是通过底层的模板创建
的 一些类型,例如CJLPerson
objc_class
的定义:
struct objc_class : objc_object {// Class ISA; //8字节Class superclass; //Class 类型 8字节cache_t cache; // formerly cache pointer and vtableclass_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
}
-
isa
属性:继承自objc_object
的isa
,占8
字节 -
superclass
属性:Class
类型,Class
是由objc_object
定义的,是一个指针
,占8
字节 -
cache
属性:简单从类型class_data_bits_t
目前无法得知,而class_data_bits_t
是一个结构体
类型,结构体
的内存大小
需要根据内部的属性
来确定,而结构体指针才是8字节
-
bits
属性:只有首地址
经过上面3个属性的内存大小总和的平移,才能获取到bits
类信息的静态与动态存储(ro、rw、rwe机制)
初始时,只有ro
。
当需要修改类信息的时候,rw
引用ro
,并拷贝一部分信息。
将需要动态更新的部分提取出来,存⼊rwe
:
总结:程序加载时方法存在ro
。当类第一次使用的时候,rw
就会引用ro
;如果动态修改,就会从ro
拷贝到rwe
;修改的时候也是去操作rwe
;
rw和ro的区别:ro
放在纯净的内存空间(clean memory),是只读的。rw
在运行生成,存放在可以读写的内存空间中(dirty memory),一经使用,ro
就会成为rw
的一部分(通过指针引用)。并且rw
对于真正需要修改的内容,还会拆分出class_rw_ext_t
;简称rwe
。
cache
其中,cache类的内存大小为12+2+2=16字节。分别有:buckets(8字节),mask(4字节)或者_maskAndBuckets(8字节), _mask_unused(4字节) 加上 _flag(2字节), _occupied(2字节)。
注意:buckets是方法缓存中的一个哈希表数组,储存了SEL和IMP。而mask使用来计算哈希索引的。这两个可以快速定位方法缓存。
sel-imp
是在cache_t
的_buckets属性
中,而在cache_t结构体中提供了获取_buckets
属性的方法buckets()
我们可以通过相应的获取方法sel()
以及 imp(pClass)
获得对应的sel-imp
bits
类的首地址平移32字节即可得到bits,bits储存信息的类型是class_rw_t
,其中可以获取属性列表和方法列表
通过由class_rw_t
提供的propertoes
方法,可以获取到一个实际类型
为property_array_t
的变量,其中有一个list的类型是property_list_t
,是一个指针,存储了 property_list
,即属性列表。所以bits中储存了属性列表。
通过由class_rw_t
提供methods
方法, 可以获取到一个实际类型
为method_array_t
的变量,其中的list指针,类型是method_list_t
,里面就是具体的方法列表
。注意,该列表里面只有实例方法。类方法存储在元类的bits属性中,获取方法与该方法相同,毕竟类是元类的实例。
在class_rw_t中,还有一个ro
方法,其返回类型是class_ro_t
,其中有一个类型为ivar_list_t
的ivars
属性,其中储存着成员变量列表。该成员变量列表不仅储存着{}定义的成员变量,还存储着属性定义的成员变量。
继承链
首先是最经典的图片:
-
isa走位
isa的走向有以下几点说明:
实例对象(Instance of Subclass)
的isa
指向类(class)
类对象(class)
isa
指向元类(Meta class)
元类(Meta class)
的isa
指向根元类(Root metal class)
根元类(Root metal class)
的isa
指向它自己
本身,形成闭环
,这里的根元类
就是NSObject
superclass走位
superclass(即继承关系)的走向也有以下几点说明:
-
类之间的继承关系
类(subClass)
继承自父类(superClass)
父类(superClass)
继承自根类(RootClass)
,此时的根类是指NSObject
根类
继承自nil
,所以根类
即NSObject
可以理解为万物起源
,即无中生有
-
元类
也存在继承
,元类之间的继承关系如下:-
子类的元类(metal SubClass)
继承自父类的元类(metal SuperClass)
-
父类的元类(metal SuperClass)
继承自根元类(Root metal Class
-
根元类(Root metal Class)
继承于根类(Root class)
,此时的根类是指NSObject
-
-
【注意】
实例对象
之间没有继承关系
,类
之间有继承关系
isKindOfClass和isMemberOfClass
isKindOfClass:
- 一切皆从调用者obj的isa开始,然后顺着superclass走下去,直到找到cls或superclass为nil结束
- 当superclass为nil,意味着最后的根类NSObject也不是cls,返回flase。
类调用:
判断顺序: SubClass -> MetaClass->MetaClass->...->RootMetaClass->NSObject
其中, 类对象调用方法的本质 是判断 cls
是不是 元类的继承链
上的任意一个
元类调用:
MetaClass
的 ISA
指向 RootMetaClass
,所以从 RootMetaClass
开始比较判断是不是我们传入的cls,如果不是,再看根类NSObject是不是,如果NSObject也不是,就彻底没有了,返回false,
根元类的父类是NSObject
cls判断顺序如图:MetaClass -> RootMetaClass->NSObject
**对象调用:**cls判断顺序如图所示:object -> SubClass -> SubClass ->...->NSObject
本质是判断 cls
是不是 类继承链
上的任意一个
isMemberofClass
不用像isKindOfClass循环直到找到或nil,他只要比较cls是不是我当前的isa指向,是返回true,不是返回false。
这个方法单纯地用来判断,cls是不是调用者的isa !!
类对象调用:
传入任何类对象都是false,因为类的isa指向元类
元类对象调用:
元类的isa指向根元类NSObjcet,传入任何类对象也都是false
对象调用:
只要判断对象的isa,也就是图中的SubClass是不是我们传入的cls
- 只判断自己的类对象是不是传入的cls
- 只接受类对象传入 ,因为没有isa,不存在元类的介入
总结
isMemberOfClass
-
对象调用:判断对象的isa(类对象)是否等于传入的cls,只判断本类,不查父类。
- 查询链:对象 → isa(类对象)== cls ?
-
类对象调用:判断类对象的isa(元类)是否等于cls。
- 查询链:类对象 → isa(元类)== cls ?
-
元类对象调用:判断元类对象的isa(根元类)是否等于cls。
- 查询链:元类对象 → isa(根元类)== cls ?
isKindOfClass
-
对象调用:判断对象的isa(类对象)是否等于cls,不等则沿superclass链查找父类,直到NSObject或nil。
- 查询链:对象 → isa(类对象)→ superclass → … → NSObject → nil
-
类对象调用:判断类对象的isa(元类)及其superclass链是否等于cls,最终会查到NSObject。
- 查询链:类对象 → isa(元类)→ superclass(父类元类)→ … → 根元类 → superclass(NSObject类对象)→ nil
-
元类对象调用:判断元类对象的isa(根元类)及其superclass链是否等于cls,最终会查到NSObject类对象。
- 查询链:元类对象 → isa(根元类)→ superclass(NSObject类对象)→ nil