OC 底层探索 03、对象本质 & isa结构
本文开始探索 OC 对象的本质是什么?
一、对象
对象的本质 - 结构体
1、编译后的对象
1、我们在 main.m 文件中做简单代码如下:
#import <Foundation/Foundation.h> #import <objc/runtime.h> @interface MyPerson : NSObject @property (nonatomic, copy) NSString *name; @end @implementation MyPerson @end int main(int argc, const char * argv[]) { @autoreleasepool { // insert code here... NSLog(@"Hello, World!"); } return 0; }
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 结构体:
struct NSObject_IMPL { Class isa; };
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 常用命令
// 把目标文件 main.m 编译成c++文件 clang -rewrite-objc main.m -o main.cpp // 若 编译时 UIKit 报错 --> 找到路径 clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot / Applications/Xcode.app/Contents/Developer/Platforms/ iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.0.sdk main.m // `xcode`安装的时候顺带安装了`xcrun`命令,`xcrun`命令在`clang`的基础上进行了 一些封装,要更好用一些 xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp // (模拟器) xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main- arm64.cpp // (真机)
二、联合体位域
结构体(struct):结构体中所有变量是共存的,且大小至少是所有变量所占内存的的和。
优点:变量共存,有容乃大;
缺点:对内存的分配是粗略的,存在空间浪费的。
联合体(union):联合体中,变量是互斥关系,即同一时间只能有一个变量。
优点:内存使用更精细灵活,节省内存空间;
缺点:互斥性,只有一个变量存在。
示例:
位域:变量每一位所代表的信息。
一字节的分配:
联合体位域大大的优化节省了内存空间。
三、isa 结构
isa 指针 8 字节,64 位,可存储信息:264 ,完全足够我们的地址存储了。
通过之前 alloc 流程探索我们可知,对象与 isa 关联是 initInstanceIsa() 方法,我们通过源码跟踪进去:
initInstanceIsa --> initIsa() --> isa_t:
联合体 isa_t :
union isa_t {
// 2个初始化 isa_t() { } isa_t(uintptr_t value) : bits(value) { } Class cls; uintptr_t bits;// cls bits 两个互斥的变量 #if defined(ISA_BITFIELD) struct { ISA_BITFIELD; // defined in isa.h }; #endif };
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():
inline Class objc_object::ISA() { ASSERT(!isTaggedPointer()); #if SUPPORT_INDEXED_ISA if (isa.nonpointer) { uintptr_t slot = isa.indexcls; return classForIndex((unsigned)slot); } return (Class)isa.bits;#else return (Class)(isa.bits & ISA_MASK);// shiftcls #endif }
3:手动位运算验证
如下图:
补图中漏写文字:$11 = 4294975728 10进制 --> 16进制 0x00000001000020f0
p/u cls (Class) $15 = 4294975728 MyPerson // 等于它
验证 over。
NSObject 中 Class isa:
NSObject 中 class 对象,我们拿到的是 class 对象,其实是通过 isa_t 拿到的,只是做了处理转化成了class。NSObject 中代码:
@interface NSObject <NSObject> { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wobjc-interface-ivars" Class isa OBJC_ISA_AVAILABILITY; #pragma clang diagnostic pop }
拿到 class 的过程:NSObject --> Class isa --> object_getclass --> ISA():
return 的是有做 Class 强转的 --> NSObject 中 Class isa OBJC_ISA_AVAILABILITY;