重识alloc流程中篇着重分析了下alloc的拦截优化处理,以及内存分配的计算方式,本篇首先还是继续先补充一下关于内存的问题
一.联合体-位域
1. 首先还是介绍一下联合体的内存分配特点
- 联合体中可以定义多个成员,联合体的大小由最大的成员大小决定
- 联合体的成员公用一个内存,一次只能使用一个成员
- 对某一个成员赋值,会覆盖其他成员的值
- 存储效率更高,可读性更强,可以提高代码的可读性,可以使用位运算提高数据的存储效率
接下来就到了论证环节
和结构体对比了一下,结果显而易见,联合体取最大成员大小,接下来在验证各个成员实际情况,这里我设置的是3个int类型数据,接下来一步步走下去
可以发现teacher2的地址存储的值是100,但是你会发现左边查看各个成员的值都是100,继续往下走
果然是替换了之前存储的数据,共用一块内存,并且发现如果联合体中成员的数据类型相同,那么他们都可以取出这块内存的数据,无论你最后是通过那个成员去赋的值
2. 位域(释义:来自百度百科)
位域特点概括
- 可以使数据单元节省储存空间
- 位段可以很方便的访问一个整数值的部分内容从而可以简化程序源代码。
接下来还是举例论证,如图所示,分别创建LGCar1,LGCar2,LGCar3,而前面2个根据计算各位分别是占用9,8个二进制位,自然而然所需内存就是2,1字节,最后一个联合体位域搭配使用,最终也只需要1字节
答案揭开
结果和我们计算的一样,而且也会发现,联合体LGCar3中的成员bits和位域的类型都是char,所以内存地址的值也是可以共用的,其实大多时候我们会使用联合体位域的时候可能都会牵扯到C语言的位运算,讲到这里大家会奇怪,说好的alloc底层流程讲解呢,怎么越扯越远了,接下来就要引入本文第一个重点也就是之前我们常常提到的isa,在重识alloc流程上篇中,提到了alloc,在底层的2个核心函数,其中之一就是_class_createInstanceFromZone,如图
内存的分配,开辟空间的计算在重识alloc流程中篇已经做了详细的介绍,那么接下来自然而然到了分析isa了,那是老规矩,走进去瞧一瞧
啊这啊这,这不点进去瞄一眼?
此时此刻,掌声在哪里?(66666666…),果然前面讲联合体不是乱说的吗,其实isa本身就是一个联合体哦
二.ISA的分析与运算
1.ISA的定义
首先展示一下ISA在 arm64及 x86_64架构下的定义
//真机
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_HAS_CXX_DTOR_BIT 1
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
# endif
# elif __x86_64__
//macOS
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_HAS_CXX_DTOR_BIT 1
# 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 unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
# else
# error unknown architecture for packed isa
# endif
- nonpointer:表示是否对isa指针开启指针优化 0:纯isa指针,1:不⽌是类对象地址,isa中包含了类信息、对象的引⽤计数等;
- has_assoc:关联对象标志位,0没有,1存在;
- has_cxx_dtor:该对象是否有C++或者Objc的析构器,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快的释放对象;
- shiftcls:存储类指针的值。开启指针优化的情况下,在arm64架构中有33位⽤来存储类指针;
- magic:⽤于调试器判断当前对象是真的对象还是没有初始化的空间;
- weakly_referenced:志对象是否被指向或者曾经指向⼀个ARC的弱变量,没有弱引⽤的对象可以更快释放;
- deallocating:标志对象是否正在释放内存;
- extra_rc:当表示该对象的引⽤计数值,实际上是引⽤计数值减1,例如,如果对象的引⽤计数为10,那么extra_rc为9。如果引⽤计数⼤于10,则需要使⽤到下⾯的has_sidetable_rc。
- has_sidetable_rc:当对象引⽤技术⼤于10时,则需要借⽤该变量存储进位;
附一张元素分布图以x86_64_为例
2.ISA的位运算
通过上述的ISA 定义里面所说的shiftcls 是存储类指针的值,接下来我们进行求证,通过ISA的位运算拿到shiftcls。以macOS为例,shiftcls在64位结构中的位置:右边有3位,左边有17位。自己包含44位。
运算方式:通过先右移3位,在左移20位,就可以只保存中间的44位,再右移17位还原之前44位原本的位置,这样就可以读取只包含这44位地址的内容了
通过运算结果也得出shiftcls 确实是存储类指针的值,并且是共用同一块内存地址的
三.对象的本质
再说到对象的本质之前,就不得不提到Clang编译器了
1.Clang概括
- Clang 是一个C语言、C++、Objective-C语言的轻量级编译器,是由Apple主导编写的
- Clang 主要用于把源文件编译成底层文件,比如把main.m 文件编译成main.cpp、main.o或者可执行文件。便于观察底层的逻辑结构,便于我们探究底层。
具体来源百度百科
2.获取.cpp文件
首先还是来一波朴实的代码
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface YCXPerson : NSObject
@property (nonatomic, strong) NSString *YCXName;
@end
@implementation YCXPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}
然后打开终端,找到工程对应目录,输入clang -rewrite-objc main.m -o main.cpp
这样就会生成一个mian.cpp文件,打开走起
全局搜索YCXPerson有惊喜哦😄
对,你没有看错,这是个结构体!从这里,就能得出,对象在底层的本质就是一个结构体。而且typedef struct objc_object YCXPerson;,也在证实着这一点
接着走,接着走,这个NSObject_IMPL成功的引起了我的注意,在全局搜一下
喜提isa一枚,所以说其实这个结构体NSObject_IVARS里面其实就只有一个成员变量就是isa
继续找继续找,搜id,SEL,Class
啧啧啧,原来id,SEL,Class 都是结构体指针,现在回想一下,似乎我们平时使用id,SEL,Class时为什么没有加 *,原来在底层已经定义为objc_class也就是结构体指针了,既然成员变量都找到了,是不是也应该把他的属性YCXName对应的setter,gettet方法也瞄一眼呢,走起来
这个写法,相信大家应该都很熟悉了,其实这些参数就是我们OC方法中的一些隐藏参数,对于alloc的探索基本就写到这里了,当然这其中其实还有很多地方可以摸索,如果后续还有新发现,会继续补充!