想要成为一名
iOS开发高手
,免不了阅读源码。如下是笔者在OC源码探索
中梳理的一个小系列——类与对象篇,欢迎你们阅读指正,同时也但愿对你们有所帮助。程序员
- OC源码分析之对象的建立
- OC源码分析之isa
- 未完待续...
isa
介绍isa
是什么在 OC源码分析之对象的建立 一文中,咱们知道alloc
底层会调用calloc
分配内存,接着就是initInstanceIsa(cls, hasCxxDtor)
,顾名思义是初始化对象的isa,其关键代码以下数据结构
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
assert(!cls->instancesRequireRawIsa());
assert(hasCxxDtor == cls->hasCxxDtor());
// 留意这里的true
initIsa(cls, true, hasCxxDtor);
}
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
assert(!isTaggedPointer());
if (!nonpointer) {
isa.cls = cls;
} else {
assert(!DisableNonpointerIsa);
assert(!cls->instancesRequireRawIsa());
isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
assert(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
}
复制代码
Tagged Pointer
听说,为了节省内存和提升执行效率,苹果提出了Tagged Pointer
的概念。对于 64 位程序,引入Tagged Pointer
后,相关逻辑能减小一半的内存占用,以及 3倍 的访问速度提高,100倍 的建立、销毁速度提高。架构
Tagged Pointer
首次应用于iPhone 5s
设备上,如今几乎都应用Tagged Pointer
了。想了解更多关于Tagged Pointer
的内容可戳 深刻理解 Tagged Pointer。ide
isa_t
类型点击objc_object::initIsa()
中的isa
,发现isa
是isa_t
类型函数
struct objc_object {
private:
isa_t isa;
... // 一些isa的公有、私有方法
};
复制代码
而isa_t
其实是一个union
(即联合体,也叫共用体)源码分析
这里先普及一下
struct
和union
的区别post
- 二者均可以包含多个不一样类型的数据,如
int
、double
、Class
等。- 在
struct
中各成员有各自的内存空间,一个struct
变量的内存总长度大于等于各成员内存长度之和;而在union
中,各成员共享一段内存空间,一个union
变量的内存总长度等于各成员中内存最长的那个成员的内存长度。- 对
struct
中的成员进行赋值,不会影响其余成员的值;对union
中的成员赋值时,每次只能给一个成员赋值,同时其它成员的值也就不存在了。
isa_t
包含了cls
和bits
两个成员变量,其结构以下性能
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
复制代码
isa
的bits
成员变量这里普及一下位域的概念优化
位域是一种数据结构,能够把数据以位的形式紧凑的储存,并容许程序员对此结构的位进行操做。ui
- 优势:
- 节省储存空间;
- 能够很方便的访问一个整数值的部份内容从而能够简化程序源代码。
- 缺点:
- 其内存分配与内存对齐的实现方式依赖于具体的机器和系统,在不一样的平台可能有不一样的结果,这致使了位段在本质上是不可移植的。
isa
的bits
成员变量类型是uintptr_t
,它实质上是个unsigned long
typedef unsigned long uintptr_t;
复制代码
在64位
CPU架构下bits
长度为64位,也就是8字节,其各个位的存储就使用了位域,即ISA_BITFIELD
。
ISA_BITFIELD
接下来看一下ISA_BITFIELD
的源码(因为笔者是用macOS
项目研究OC底层源码,因此这里以x86_64
架构为例)
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_BITFIELD \ uintptr_t nonpointer : 1; \ uintptr_t has_assoc : 1; \ uintptr_t has_cxx_dtor : 1; \ uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \ uintptr_t magic : 6; \ uintptr_t weakly_referenced : 1; \ uintptr_t deallocating : 1; \ uintptr_t has_sidetable_rc : 1; \ uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
复制代码
首先明确一点,在64位
CPU架构下isa指针
的长度也是8字节
,它能够存储足够多的内容,苹果为了优化性能,存储类地址只用了一部分位(x86_64
下是44位,arm64
下是33位),剩下的位用来存储一些其它信息。
具体分析一下ISA_BITFIELD
位域各成员的表示意义:
nonpointer
:表示是否对 isa指针
开启指针优化。
isa指针
,当访问isa
指针时,直接返回其成员变量cls
isa 指针
内容不止是类地址,还包含了类的一些信息、对象的引用计数等。has_assoc
:是否有关联对象。has_cxx_dtor
:该对象是否有C++或Objc的析构器。
shiftcls
:存储类指针的值。开启指针优化的状况下,在 x86_64
架构有 44位 用来存储类指针,arm64
架构中有 33位 。magic
:用于调试器判断当前对象是真的对象,仍是一段没有初始化的空间。weakly_referenced
:用于标识对象是否被指向或者曾经被指向一个ARC
的弱变量,没有弱引用的对象释放的更快。deallocating
:标识对象是否正在释放内存。has_sidetable_rc
:对象的引用计数值是否有进位。extra_rc
:表示该对象的引用计数值。extra_rc
只是存储了额外的引用计数,实际的引用计数公式:实际引用计数 = extra_rc + 1
。这里占了8位,因此理论上能够存储的最大引用计数是:2^8 - 1 + 1 = 256
(arm64
CPU架构下的extra_rc
占19位,可存储的最大引用计数为2^19 - 1 + 1 = 524288
)。
has_sidetable_rc
的关联:当对象的最大引用计数超过界限后,has_sidetable_rc
的值为1,不然为0isa
的cls
成员变量分析完isa
的位域,接下来就只剩下cls
,它是Class
类型,一样上源码
typedef struct objc_class *Class;
// 顺便了解一下id的类型,显然id是个指针变量,它的值只有一个isa变量
typedef struct objc_object *id;
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache;
class_data_bits_t bits;
class_rw_t *data() {
return bits.data();
}
... // 一些方法
};
struct objc_object {
private:
isa_t isa;
... // 一些isa的公有、私有方法
};
复制代码
从源码得知,Class
其实是objc_class
结构体的指针变量,而objc_class
又继承自objc_object
,所以Class
这个结构体指针变量的值内部有一个isa
成员变量(类型为isa_t
),这个isa
成员变量在64位
CPU架构下是8字节,且排在objc_class
结构体的前8字节。
isa
的做用经过对isa
的位域说明,咱们知道shiftcls
存储的是类指针的值。在x86_64
架构下,shiftcls
占用44位,也就是第[3, 46]位。将 [3, 46]位 所有填充1,[0, 2]位 和 [47~63]位 都补0,获得0x7ffffffffff8,也就是ISA_MASK
的值。故,isa & ISA_MASK
会获得shiftcls
存储的类指针的值。这也就是所谓MASK
的做用。
以下图所示
下面用一个例子说明isa
的做用
此时经过lldb
命令调试
说明:
0x001d800100001129
是对象p
的isa
值,经过isa & ISA_MASK
运算获得的0x0000000100001128
就是Person
类的地址- 证实【1】:经过
p/x Person.class
直接打印Person
类地址,显然获得的是0x0000000100001128
,如此【1】证实成立!
结论:isa
将对象和类关联起来,起到了中间桥梁的做用。
思考:若是不用
ISA_MASK
,那么如何证实isa
的这个做用呢?——答案将在文末补充。
isa
的初始化补充最后补充一下isa
的初始化。还记得初始化isa
的入口吗?是initIsa(cls, true, hasCxxDtor);
,此时nonpointer
的值是true
,再看SUPPORT_INDEXED_ISA
的定义
#if __ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__)
# define SUPPORT_INDEXED_ISA 1
#else
# define SUPPORT_INDEXED_ISA 0
#endif
复制代码
在x86_64
下,SUPPORT_INDEXED_ISA
是0,因此isa
的初始化最终会来到
isa_t newisa(0);
// 使用ISA_MAGIC_VALUE(0x001d800000000001ULL)赋值给bits
// nonpointer为1,magic为1d,其余变量为零
newisa.bits = ISA_MAGIC_VALUE;
// hasCxxDtor是从类的isa中取出的
newisa.has_cxx_dtor = hasCxxDtor;
// 将cls右移3位后赋值给shiftcls
newisa.shiftcls = (uintptr_t)cls >> 3;
isa = newisa;
复制代码
碍于篇幅,这里不继续深刻
hasCxxDtor
。
isa
指向图经过以上的源码分析,咱们认识到对象的isa指针
指向了对象所属的类。而类自己也有一个isa
指针,它指向的又是什么呢?
苹果官方有个isa
指向图,即
从图可知,类的isa指针
指向的是类的元类。如今咱们来验证一下吧。
建立Teacher
类、Person
类,其中Person
类继承于NSObject
,Teacher
类继承于Person
类。
对比
isa指向图
,对号入座后就是,Teacher
类至关于Subclass
,Person
类至关于Superclass
,NSObject
至关于Root class
。
teacher
对象的类(结果是Teacher
类,地址为0x0000000100001230
)(lldb) x/4gx teacher
0x100f59400: 0x001d800100001231 0x0000000000000000
0x100f59410: 0x636f72504b575b2d 0x70756f7247737365
(lldb) p/x 0x001d800100001231 & 0x00007ffffffffff8 // isa & ISA_MASK
(long) $3 = 0x0000000100001230 // 对象teacher的类地址
(lldb) po $3
Teacher // 对象teacher的类
复制代码
Teacher
类的元类(结果是Teacher元类
,地址为0x0000000100001208
)(lldb) x/4gx Teacher.class
0x100001230: 0x001d800100001209 0x00000001000011e0
0x100001240: 0x0000000100f61150 0x0000000100000003
(lldb) p/x 0x001d800100001209 & 0x00007ffffffffff8 // isa & ISA_MASK
(long) $5 = 0x0000000100001208 // Teacher类的元类地址
(lldb) po $5
Teacher // Teacher类的元类
复制代码
Teacher类 和 Teacher元类 地址不同
person
对象的类(结果是Person
类,地址为0x00000001000011e0
),以及类的元类(结果是Person元类
,地址为0x00000001000011b8
)(lldb) x/4gx person
0x100f60a30: 0x001d8001000011e1 0x0000000000000000
0x100f60a40: 0x0000000000000002 0x00007fff9b855588
(lldb) p/x 0x001d8001000011e1 & 0x00007ffffffffff8 // isa & ISA_MASK
(long) $8 = 0x00000001000011e0 // 对象person的类地址
(lldb) po $8
Person // 对象person的类
(lldb) x/4gx Person.class
0x1000011e0: 0x001d8001000011b9 0x0000000100b38140
0x1000011f0: 0x0000000100f61030 0x0000000100000003
(lldb) p/x 0x001d8001000011b9 & 0x00007ffffffffff8 // isa & ISA_MASK
(long) $10 = 0x00000001000011b8 // Person类的元类地址
(lldb) po $10
Person // Person类的元类
复制代码
object
对象的类(结果是NSObject
类,地址为0x0000000100b38140
),以及类的元类(结果是NSObject元类
,地址为0x0000000100b380f0
)(lldb) x/4gx object
0x100f5cc50: 0x001d800100b38141 0x0000000000000000
0x100f5cc60: 0x70736e494b575b2d 0x574b57726f746365
(lldb) p/x 0x001d800100b38141 & 0x00007ffffffffff8 // isa & ISA_MASK
(long) $12 = 0x0000000100b38140 // 对象object的类地址
(lldb) po $12
NSObject // 对象object的类
(lldb) x/4gx NSObject.class
0x100b38140: 0x001d800100b380f1 0x0000000000000000
0x100b38150: 0x0000000101913060 0x0000000200000003
(lldb) p/x 0x001d800100b380f1 & 0x00007ffffffffff8 // isa & ISA_MASK
(long) $14 = 0x0000000100b380f0 // NSObject类的元类地址
(lldb) po $14
NSObject // NSObject类的元类
复制代码
Teacher元类
的元类,Person元类
的元类,以及NSObject元类
的元类(lldb) x/4gx 0x0000000100001208 // Teacher元类
0x100001208: 0x001d800100b380f1 0x00000001000011b8
0x100001218: 0x000000010186f950 0x0000000400000007
(lldb) p/x 0x001d800100b380f1 & 0x00007ffffffffff8 // isa & ISA_MASK
(long) $16 = 0x0000000100b380f0 // NSObject元类
(lldb) po $16
NSObject // NSObject元类
(lldb) x/4gx 0x00000001000011b8 // Person元类
0x1000011b8: 0x001d800100b380f1 0x0000000100b380f0
0x1000011c8: 0x0000000101905a50 0x0000000300000007
(lldb) p/x 0x001d800100b380f1 & 0x00007ffffffffff8 // isa & ISA_MASK
(long) $17 = 0x0000000100b380f0 // NSObject元类
(lldb) po $17
NSObject // NSObject元类
(lldb) x/4gx 0x0000000100b380f0 // NSObject元类
0x100b380f0: 0x001d800100b380f1 0x0000000100b38140
0x100b38100: 0x0000000101903820 0x0000000500000007
(lldb) p/x 0x001d800100b380f1 & 0x00007ffffffffff8 // isa & ISA_MASK
(long) $18 = 0x0000000100b380f0 // NSObject元类
(lldb) po $18
NSObject // NSObject元类
复制代码
isa
指向结论基于【2.2】的验证过程,能够得出结论:
isa指针
指向 对象的所属类(如person
对象的isa
指向Person
类)isa指针
指向 类的元类(如Person
类的isa
指向Person元类
)isa指针
指向 根元类(如Person元类
的isa
指向NSObject元类
)
NSObject类
的元类是根元类
NSObject元类
的isa指针
指向自身(是个圆圈)思考:若是Person类继承的是NSProxy,相关isa指向是怎样的呢?感兴趣的能够去试试。
类的继承关系证实过程:(以 -> 表示 继承自)
(lldb) p class_getSuperclass(Teacher.class)
(Class) $19 = Person // Teacher类 -> Person类
(lldb) p class_getSuperclass(Person.class)
(Class) $20 = NSObject // Person类 -> NSObject类
(lldb) p class_getSuperclass(NSObject.class)
(Class) $21 = nil // NSObject类 -> nil
复制代码
元类的继承关系证实过程:(以 -> 表示 继承自)
// 0x0000000100001208 是 Teacher元类
(lldb) p/x class_getSuperclass((Class)0x0000000100001208)
(Class) $17 = 0x00000001000011b8 // Person元类
(lldb) po $17
Person // Teacher元类 -> Person元类
// 0x00000001000011b8 是 Person元类
(lldb) p/x class_getSuperclass((Class)0x00000001000011b8)
(Class) $22 = 0x0000000100b380f0 // NSObject元类(根元类)
(lldb) po $22
NSObject // Person元类 -> 根元类
// 0x0000000100b380f0 是 根元类
(lldb) p/x class_getSuperclass((Class)0x0000000100b380f0)
(Class) $23 = 0x0000000100b38140 NSObject // NSObject类(根类)
(lldb) po $23
NSObject // 根元类 -> 根类
复制代码
根元类继承自根类(NSObject元类 -> NSObject类),根类继承自nil(NSObject类 -> nil)
isa
指向图涂鸦版把上面的例子涂在isa
指向图上,就获得了下图
isa
是isa_t
结构,采用 联合体+位域 的搭配来设计:在不一样的位上显示不一样的内容,以此来节省储存空间,进而优化内存。isa
包含了cls
和bits
两个成员变量,这两个成员变量在64位
CPU架构下的长度都是8字节,因此isa
在64位
CPU架构下的长度也是8字节。isa
的位域上存储了一些对象与类的信息,并将对象与类关联起来,起到中间桥梁的做用。isa
指向图相关结论:
isa指针
指向 对象的所属类(如person
对象的isa
指向Person
类)isa指针
指向 类的元类(如Person
类的isa
指向Person元类
)isa指针
指向 根元类(如Person元类
的isa
指向NSObject元类
)
根元类
的isa指针
指向自身(是个圆圈)Teacher元类
继承自 Person元类
)
根元类
继承自 根类
根类
继承自 nil
isa
的做用的证实2Q:若是不用ISA_MASK
,那么如何证实isa
关联了对象和类的做用呢?
A:具体思路是,shiftcls
在x86_64
架构下长度是44位,存储在isa
的 [3, 46]位上,因此能够经过将isa
的 [0, 2]位、[47, 63]位清零,一样能获得shiftcls
的值,进而肯定类。
如图所示,通过对isa
的一番运算,成功获得与Person
类相同的地址。
NSProxy
的isa
指向Q:若是Person类继承的是NSProxy,相关isa指向是怎样的呢?
A:跟NSObject
同样,二者都是根类
。