iOS底层学习-对象本质 & nonPointerIsa分析

这是我参与8月更文挑战的第3天,活动详情查看:8月更文挑战

一、对象的本质

1、clang

  • Clang是⼀个C语⾔、C++、Objective-C语⾔的轻量级编译器。源代码发布于BSD协议下。Clang将⽀持其普通lambda表达式、返回类型的简化处理以及更好的处理constexpr关键字。
  • Clang是⼀个由Apple主导编写,基于LLVM的C/C++/Objective-C编译器

clang 命令

// 把目标编译成C++文件
clang -rewrite-objc main.m -o main.cpp 
//UIKit报错
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.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 
复制代码

2、探索对象的本质

2.1、对象的本质是结构体

引入一个示例,在main.m文件中添加LRPerson类的声明和实现
图片[1]-iOS底层学习-对象本质 & nonPointerIsa分析-一一网
打开终端,cd至当前目录下,用如下命令将main.m编译成main.cpp文件

clang -rewrite-objc main.m -o main.cpp 
复制代码

main.cpp代码非常多,十万多行。全局搜索下LRPerson,发现一个结构体LRPerson_IMPL,里面正好有_name。经验证发现对象的本质是结构体
图片[2]-iOS底层学习-对象本质 & nonPointerIsa分析-一一网
LRPerson_IMPL中除了成员变量_name,还有一个NSObject_IVARS,是一个NSObject_IMPL的结构体,搜索下NSObject_IMPL,可以看到定义如下:
图片[3]-iOS底层学习-对象本质 & nonPointerIsa分析-一一网
所以NSObject_IVARS就是isa

2.2、拓展

1、在LRPerson_IMPL结构体上面有这样一行代码,可以看到LRPersonobjc_object类型,这里的objc_object就是NSObject的底层实现。

typedef struct objc_object LRPerson;
复制代码

2、同样可以发现在底层中Classobjc_class类型的结构体指针;我们常用的idobjc_object类型的结构体指针,这也解释了id类型可以修饰任何对象;SEL也是一个结构体指针。

typedef struct objc_class *Class;
typedef struct objc_object *id;
typedef struct objc_selector *SEL;
复制代码

3、在LRPerson_IMPL结构体下面可以看到如下一段代码,这里是name属性的get/set方法
图片[4]-iOS底层学习-对象本质 & nonPointerIsa分析-一一网
可以发现get/set方法都有两个隐藏参数self_cmd,也就是方法接收者方法编号
get/set方法是根据对象首地址和成员变量的偏移值来读取和赋值的。

二、联合体位域

先了解下联合体和位域

struct LRCar1 { 
    BOOL front; 
    BOOL back; 
    BOOL left; 
    BOOL right; 
};
复制代码

这是一个表示方向的结构体LRCar1BOOL类型占用1字节,这个结构体占用4字节32位。对于一个BOOL类型,值只有01LRCar1只需要占用4位就可以表示4个方向。而1字节 = 8位,所以LRCar11字节就可以满足需求,这里占用了4字节显然浪费了3个字节的空间。那这里如何优化呢?下面我们引入位域的概念

1、位域

struct LRCar2 { 
    BOOL front: 1; 
    BOOL back : 1; 
    BOOL left : 1; 
    BOOL right: 1; 
};
复制代码

LRCar2LRCar1相比,每个成员后面多了个: 1,冒号后面的数字代表成员占用多少位。使用位域后LRCar2只占用4位也就是1字节,我们打印验证一下

2、联合体

先观察下面结构体和联合体的对比,先断点打印结构体teacher1每个成员变量的赋值可以发现,结构体赋值过程中每个成员之前是互不影响的,共存在这个结构体中。
图片[5]-iOS底层学习-对象本质 & nonPointerIsa分析-一一网
而我们发现联合体teacher2在赋值过程中,成员变量间会受到影响,呈互斥状态。
图片[6]-iOS底层学习-对象本质 & nonPointerIsa分析-一一网
分别打印一下结构体teacher1和联合体teacher2各成员变量的地址。可以发现结构体成员变量有自己的内存空间,而联合体成员变量共用同一个内存空间,所以联合体成员变量间互斥。

总结

  • 结构体(struct)中所有变量是“共存”的——优点是“有容乃⼤”,全⾯;缺点是struct内存空间的分配是粗放的,不管⽤不⽤,全分配。
  • 联合体(union)中是各变量是“互斥”的——缺点就是不够“包容”;但优点是内存使⽤更为精细灵活,也节省了内存空间

三、nonPointerIsa分析

在第一篇alloc探索中,我们得知initInstanceIsa这一步是初始化指针关联类,跟进去来到initIsa,这里就是初始化isa的方法,isaisa_t类型。
图片[7]-iOS底层学习-对象本质 & nonPointerIsa分析-一一网
先看下isa_t
图片[8]-iOS底层学习-对象本质 & nonPointerIsa分析-一一网
我们发现isa_t是一个联合体,包含bitscls,还有一个结构体ISA_BITFIELD

普遍在表现一个类地址过程中,经常会出现一个有意思的名词叫nonPointerIsa。平时说的类也是一个对象,是个指针。我们发现类上可以有很多信息可以存储,指针是8字节64位,如果只存一个指针,未免有点浪费。是否可以优化一下呢?在类中除了指针还可以有其他东西,比如是否正在释放、引用计数、weak、关联对象、析构函数等等,这就出现了nonPointerIsanonPointerIsa不再是一个简简单单的指针。如何验证这些呢?我们需要去看ISA_BITFIELD
图片[9]-iOS底层学习-对象本质 & nonPointerIsa分析-一一网
图片[10]-iOS底层学习-对象本质 & nonPointerIsa分析-一一网
ISA_BITFIELD针对不同平台arm64x86位域设置不同

  • nonpointer:表示是否对 isa 指针开启指针优化。0:纯isa指针,1:不⽌是类对象地址,isa 中包含了类信息、对象的引⽤计数等。
  • has_assoc:关联对象标志位。0没有,1存在。
  • has_cxx_dtor:该对象是否有 C++ 或者 Objc 的析构器。如果有析构函数,则需要做析构逻辑;如果没有,则可以更快的释放对象。
  • shiftcls: 存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位⽤来存储类指针。
  • magic:⽤于调试器判断当前对象是真的对象还是没有初始化的空间。
  • weakly_referenced:标志对象是否被指向或者曾经指向⼀个 ARC 的弱变量,没有弱引⽤的对象可以更快释放。
  • deallocating:标志对象是否正在释放内存。
  • has_sidetable_rc:当对象引⽤技术⼤于 10 时,则需要借⽤该变量存储进位。
  • extra_rc:当表示该对象的引⽤计数值,实际上是引⽤计数值减 1。例如,如果对象的引⽤计数为 10,那么extra_rc 为 9。如果引⽤计数⼤于 10,则需要使⽤ has_sidetable_rc。

四、isa推导class

图片[11]-iOS底层学习-对象本质 & nonPointerIsa分析-一一网
x/4gx打印出p,再p/x打印出LRPerson.class。我们发现打印出的isa和类并不一致。此时isa应该和类已经关联起来,但这里并没有看出什么来。原因是这里没有& ISA_MASKisa与上掩码才能得到类。是因为isa中不止是的信息,还有很多其他信息,需要通过掩码来获取类的信息

五、isa位运算

假使我们并不知道掩码,根据上面分析已经清楚isa的位域分布结构,类信息在shiftcls中,那么可以根据位运算来获取shiftcls
图片[12]-iOS底层学习-对象本质 & nonPointerIsa分析-一一网
x86_64为例,shiftcls占用44位,前边有3位,后边有17(6+1+1+1+8)

  • isa先右移3位,清空shiftcls前边3位
  • 再左移20位,清空shiftcls后边17位以及复原刚右移的3位
  • 最后再右移17位,复原到最初的位置,最后得到类的信息。可以看到打印出LRPerson
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享