前言
通过上篇文章 我们了解 alloc 底层最终调用的是 _class_createInstanceFromZone 方法开辟内存空间 下面就来分析一下该方法做了什么?
_class_createInstanceFromZone (已去掉多余代码)
static ALWAYS_INLINE id
_class_createInstanceFromZone()
{
size_t size;
size = cls->instanceSize(extraBytes);//计算内存大小
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
obj = (id)calloc(1, size);//开辟内存空间
}
if (!zone && fast) {
obj->initInstanceIsa(cls, hasCxxDtor);//初始化 isa 指针和类关联
} else {
obj->initIsa(cls);
}
//isa 指针
if (fastpath(!hasCxxCtor)) {
return obj;
}
}
对于第一个 instanceSize() 方法
inline size_t instanceSize(size_t extraBytes) const {
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);//从缓存中获取
}
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
可以看到 在有缓存的时候 fastInstanceSize() 内部是16字节对齐的算法
size_t fastInstanceSize(size_t extra) const{
return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
}
而在首次初始化对象时调用 alignedInstanceSize() 方法 最终来到 word_align 方法 可以看出这是个8字节对齐算法
#define WORD_MASK 7UL // 64 位操作系统下代表7
static inline uint32_t word_align(uint32_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
测试一下
x = 6 计算 (6 + 7) & ~7 (1011) & ~ 0111 ~0111 = 1000 相当于 (1011) & 1000 结果: 1000 结果为8字节 &~ 相当于: 右移 3 位 再左移 3 位
思考:有缓存的时候是 16字节对齐,而没有缓存的时候却是8 字节对齐,那实例对象究竟是 8字节对齐 还是16字节对齐呢?
带着这个问题 继续查看 _class_createInstanceFromZone 方法
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
obj = (id)calloc(1, size);//开辟内存空间
}
void *calloc(size_t __count, size_t __size)
- count -- 要分配的元素个数
- size -- 分配的元素大小
先看一个 calloc demo
#import <malloc/malloc.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
void *p1 = calloc(1, 10);// 分配一个长度为 10 的连续空间
void *p2 = calloc(2, 20);// 分配两个长度为 20 的连续空间
NSLog(@"%lu",malloc_size(p1));
NSLog(@"%lu",malloc_size(p2));
}
return 0;
}
控制台输出结果:
2022-05-06 14:28:56.846074+0800 OCDemo[30323:94337] 16 2022-05-06 14:28:56.846980+0800 OCDemo[30323:94337] 48
通过上面的结果得出系统实际分配内存的时候是以16字节对齐的,对象的内部则是以 8 字节对齐
为什么要内存对齐?
内存是以字节为基本单位,cpu 在存取数据时,以 块 为单位,而不是以字节为单位存取,字节对齐后会减少 cpu 存取次数,以空间换时间 可以降低 cpu 开销,提高 cpu 访问速率
通过代码 打印 对象实际所占内存大小
@interface Student : NSObject
@property(nonatomic,copy)NSString *name;//8字节
@property(nonatomic,assign)int age;//4字节
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Student *stu = [Student new];
stu.name = @"Terry";
stu.age = 18;
NSLog(@"%lu",malloc_size((__bridge const void *)stu));
}
return 0
}
2022-05-06 16:19:12.923648+0800 OCDemo[64446:197025] 32
上面的输出结果:32字节,8 + 4 + isa(8字节) = 20,16 字节对齐后为 32
对象本质
在 iOS 开发中 我们定义的对象、属性、方法在编译的时候做了什么,我们不得而知,通过 Clang 生成 cpp 文件(Swift 则是通过 SIL),可以帮助我们更好的去分析。
把目标文件编译成 c++ 文件
clang -rewrite-objc main.m
打开 main.cpp 文件 搜索 Student 类
#ifndef _REWRITER_typedef_Student
#define _REWRITER_typedef_Student
typedef struct objc_object Student;
typedef struct {} _objc_exc_Student;
#endif
extern "C" unsigned long OBJC_IVAR_$_Student$_name;
extern "C" unsigned long OBJC_IVAR_$_Student$_age;
struct Student_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _age;
NSString *_name;
};
不难发现 对象的本质就是一个叫做 objc_object 的结构体。
搜索 NSObject_IMPL
struct NSObject_IMPL {
Class isa;
};
objc_object 的结构体里面存储了:isa 指针 + 成员变量的值,
即 对象的本质是 isa 加 成员变量的值
然后跟着 _class_createInstanceFromZone 方法继续往下走

断点到这里后 通过控制台 po obj
断点到 8029 行代码 (lldb) po obj 0x0000000100a279a0 断点到 8044 行代码 (lldb) po obj <Student: 0x100a279a0>
在 8029 行代码 obj 没有与 Student 关联
当 obj 调用 initIsa()方法后
运行到 8044 行代码 obj 与 Student 进行了关联
总结
iOS 为对象开辟内存的流程:
- 通过 instanceSize() 先计算对象需要开辟的内存空间,对象内部是 8 字节对齐算法
- 系统通过 calloc() 分配实际所需内存空间 16字节对齐算法
- 最后调用 initIsa() 初始化 isa 指针 和 类进行关联
- cpu 是昂贵的资源,通过字节对齐算法 以空间换时间,降低开销,当我们看到源码字节对齐算法的时候,也要思考其背后的原因。