对象本质 & isa 结构
本文开始探索 OC 对象的本质是什么?
一、对象
对象的本质 - 结构体
1、编译后的对象
1、我们在 main.m 文件中做简单代码如下:
1 |
|
clang 将 main.m 文件编译为 C++ 文件 main.cpp,命令如下:
我们可从生成的 .cpp 文件中找到关于 MyPerson 的编译后的结构:
MyPerson 编译成了 struct MyPerson_IMP {…}
即:对象 –> 在底层编译成了结构体 struct
问:.cpp 文件中第 112268 行 ‘struct NSObject_IMPL NSObject_IVARS;’ 是什么呢?
伪继承。在 C++ 中结构体是可以继承的,在 C 中可以也可以伪继承,如上图代码:将继承结构体作为当前结构体的第一个元素。此时,结构体 MyPerson_IMP 拥有 结构体 NSObject_IMPL 的所有成员变量。
任何类里面都有 NSObject_IVARS ?–> isa
NSObject_IMPL 结构体:
1 |
|
2、同样在 .cpp 文件中也可以找到 MyPerson 的 name 属性
setter/getter 方法:
objc_setProperty(),我们通过 objc 源码跟踪进去具体实现:objc_setProperty() –> reallySetProperty()
从源码可以看出,setter 方法的操作可以简单理解为就是:新值 retain 和 旧值 release . 但需要注意代码中逻辑判断,当 copy 等属性为 true 时是不同操作。详细讲解 链接.
在这里我们可以考虑,我们所有的对象的 setter 方法,都是经历一个 retain release 过程,完全是统一的过程。那么这里我们就考虑到了将其进行类似工厂类的封装,所有的对象无需每个都分开处理,我们把它扔进来即可。即:无论外部对象如何变化,每个对象的 setter 方法都会走到 objc_setProperty() –> 一种思路。
Clang
Clang 是什么?
Clang 是一个 C 语言、C++、Object-C 语言的轻量化编译器。它是由苹果主导编写 基于 LLVM 的 C/C++/OC 编译器。早起苹果编译器是 GCC。
Clang 常用命令
1 |
|
二、联合体位域
结构体 (struct):结构体中所有变量是共存的,且大小至少是所有变量所占内存的的和。
优点:变量共存,有容乃大;
缺点:对内存的分配是粗略的,存在空间浪费的。
联合体 (union):联合体中,变量是互斥关系,即同一时间只能有一个变量。
优点:内存使用更精细灵活,节省内存空间;
缺点:互斥性,只有一个变量存在。
示例:
位域:变量每一位所代表的信息。
一字节的分配:
联合体位域大大的优化节省了内存空间。
三、isa 结构
isa 指针 8 字节,64 位,可存储信息:264 ,完全足够我们的地址存储了。
通过之前 alloc 流程探索我们可知,对象与 isa 关联是 initInstanceIsa() 方法,我们通过源码跟踪进去:
initInstanceIsa –> initIsa() –> isa_t:
联合体 isa_t :
1 |
|
ISA_BITFIELD
- 手机端:
- Mac OS:
以 arm64 为例,位域代表含义:
nonpointer – 占据第 0 位,是否对 isa 开启指针优化。0:纯 isa 指针;1:不只是类对象地址,isa 中包含了类信息、对象的引用计数等。一般我建的类都是 nopointer 的。
shiftcls – 占据第 3~35 位,为存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位用来存类指针。
has_sidetable_rc – 为 1 时,则表明有外部挂的引用计数 sideTable,下面的 extra_rc 存引用计数不够用了。
extra_rc – 引用计数值 - 1 的 值。例:对象的引用计数为 10,则 extra_rc = 9;若 extra_rc 不足以保存引用计数了,则上面的 has_sidetable_rc 为 true,使用 sideTable 管理。
2、验证 isa 与 类 关联
1:源码跟踪 验证 (本质也是位移操作)
执行可编译源码工程进入 initIsa() 函数,cls 为 MyPerson,nonpointer = ture:
newisa.bits = ISA_MAGIC_VALUE;// 赋初值
migic = 59 验证:
打开系统编程计算器:
二进制 110111 –> 10 进制 59
执行 newisa.shiftcls = (uintptr_t)cls >> 3;
cls –> MyPerson –> 强转为 uintptr_t 类型 –> 值:4294975728,为何?
计算机是不识别字符串的,需要将其转成可识别的二进制的 010101…… 的机器指令。
右移 3 位 >>3: shiftcls 从第 3 号位开始,之前 0 1 2 位有三个变量,我们取 shiftcls 时不能受其影响,故做 >>3 抹零操作。
继续运行,回到 _class_createInstanceFromZone() 函数。
我们继续验证。
isa 地址 & ISA_MASK
isa 地址 & ISA_MASK –> MyPerson
提一下 ISA_MASK:0x00007ffffffffff8ULL –> 第 3~47 位为 1,& 运算即两头抹零。
2:runtime - object_getClass() 验证
跟踪进入 object_getClass() 方法 –> getIsa() –> ISA():
1 |
|
3:手动位运算验证
如下图:
补图中漏写文字:$11 = 4294975728 10 进制 –> 16 进制 0x00000001000020f0
1 |
|
验证 over。
NSObject 中 Class isa:
NSObject 中 class 对象,我们拿到的是 class 对象,其实是通过 isa_t 拿到的,只是做了处理转化成了 class。NSObject 中代码:
1 |
|
拿到 class 的过程:NSObject –> Class isa –> object_getclass –> ISA():
return 的是有做 Class 强转的 –> NSObject 中 Class isa OBJC_ISA_AVAILABILITY;
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!