接着上篇的第三步我们继续内存分析
上篇研究到了不同类型在内容中所占用的size。我们接着第三步继续开始,我们先看两个struct
struct LGStruct1 {
double a; // 8 [0 7]
char b; // 1 [8]
int c; // 4 (9 10 11 [12 13 14 15]
short d; // 2 [16 17] 24
}struct1;
struct LGStruct2 {
double a; // 8 [0 7]
int b; // 4 [8 9 10 11]
char c; // 1 [12]
short d; // 2 (13 [14 15] 16
}struct2;
struct LGStruct3 {
double a; // 8 [0 7]
int b; // 4 [8 9 10 11]
char c; // 1 [12]
short d; // 2 (13 [14 15]
int e; // 4 [16 17 18 19]
struct LGStruct1 str; //24 (20 21 22 23 [24...46] 48
}struct3;
NSLog(@"%lu-%lu-%lu",sizeof(struct1),sizeof(struct2),sizeof(struct3));
**2021-09-02 09:13:47.620830+0800 001-内存对齐原则[20249:1936263] 24-16-48**
从打印结果上发现结构体里面的属性个数和类型是一样的,但是结构体的size却不一样。
总结:
- 属性是按照size的倍数位置进行存储,cpu按照整数倍进行读取,空间换时间,速度更快
- 对象struct是按照16字节进行对齐
再回到我们的对象中,给person增加几个属性
LGPerson *person = [LGPerson alloc];
person.name = @"stone";
person.nickName = @"ios";
person.age = 18;
person.height = 190.5;
person.c1 = 'a';
person.c2 = 'b';
NSLog(@"对象类型的内存大小--%lu",sizeof(person));
NSLog(@"对象实际的内存大小--%lu",class_getInstanceSize([person class]));
NSLog(@"系统分配的内存大小--%lu",malloc_size((__bridge const void *)(person)));
2021-09-02 09:43:11.015921+0800 001-内存对齐原则[20462:1970436] 对象类型的内存大小--8
2021-09-02 09:43:11.016650+0800 001-内存对齐原则[20462:1970436] 对象实际的内存大小--40
2021-09-02 09:43:11.016747+0800 001-内存对齐原则[20462:1970436] 系统分配的内存大小--48
可以发现苹果对于属性在存储的时候,做了一些优化,将int char公用了8个字节的内存。
再研究一下class_getInstanceSize函数是怎么计算对象的size的。依然是看objc4-818.2源码,追进去看到这个方法
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
static inline size_t word_align(size_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
class_getInstanceSize在计算好对象空间后,做了一步(x + WORD_MASK) & ~WORD_MASK 8字节对齐的操作,和对象alloc的时候,做的16字节对齐是一样的
malloc_size是返回系统给对象开辟的内存,就alloc方法入手找打calloc函数
calloc(size_t num_items, size_t size)
{
return _malloc_zone_calloc(default_zone, num_items, size, MZ_POSIX);
}
_malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size,
malloc_zone_options_t mzo)
{
MALLOC_TRACE(TRACE_calloc | DBG_FUNC_START, (uintptr_t)zone, num_items, size, 0);
void *ptr;
if (malloc_check_start) {
internal_check();
}
//这是关键,别问,问就是一下就看到了
ptr = zone->calloc(zone, num_items, size);
if (os_unlikely(malloc_logger)) {
malloc_logger(MALLOC_LOG_TYPE_ALLOCATE | MALLOC_LOG_TYPE_HAS_ZONE | MALLOC_LOG_TYPE_CLEARED, (uintptr_t)zone,
(uintptr_t)(num_items * size), 0, (uintptr_t)ptr, 0);
}
MALLOC_TRACE(TRACE_calloc | DBG_FUNC_END, (uintptr_t)zone, num_items, size, (uintptr_t)ptr);
if (os_unlikely(ptr == NULL)) {
malloc_set_errno_fast(mzo, ENOMEM);
}
return ptr;
}
发现one->calloc(zone, num_items, size);但是点不进去了,通过汇编的方式继续往下走。
就找到了关键函数
#define SHIFT_NANO_QUANTUM 4
#define NANO_REGIME_QUANTA_SIZE (1 << SHIFT_NANO_QUANTUM) // 16
static MALLOC_INLINE size_t
segregated_size_to_fit(nanozone_t *nanozone, size_t size, size_t *pKey)
{
size_t k, slot_bytes;
if (0 == size) {
size = NANO_REGIME_QUANTA_SIZE; // Historical behavior
}
//k = (size + 16 - 1) >> 4 左移4位
k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM;
// round up and shift for number of quanta
// slot_bytes = k << 4 右移4位
slot_bytes = k << SHIFT_NANO_QUANTUM;
// multiply by power of two quanta size
*pKey = k - 1;
// Zero-based!
return slot_bytes;
}
结论:最后在创建对象的时候,苹果是按照16个字节进行对齐的。
第四步 对象的本质
使用clang编译器,将oc变成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
// xcrun命令基于clang基础上进行了封装更好用
//3、模拟器编译
xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
//4、真机编译
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main- arm64.cpp
clang编译后,我们看到对象就是结构体
#ifndef _REWRITER_typedef_NSObject
#define _REWRITER_typedef_NSObject
typedef struct objc_object NSObject;
typedef struct {} _objc_exc_NSObject;
#endif
struct NSObject_IMPL {
Class isa;
};
extern "C" unsigned long OBJC_IVAR_$_LWPerson$_LWname;
extern "C" unsigned long OBJC_IVAR_$_LWPerson$_age;
struct LWPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
double height;
NSString *_LWname;
NSInteger _age;
};
// @property(nonatomic, copy)NSString *LWname;
// @property(nonatomic,assign)NSInteger age;
/* @end */
// @implementation LWPerson
struct NSObject_IMPL {
Class isa;
};
-
Class类型实际是一个objc_class类型的结构体指针,objc_class是所有类的底层实现。由此我们猜测isa可能跟类信息存在着重要的关联,具体的关联下面会进行探究。 -
NSObject的底层实现和对象的底层实现有什么区别。两者成员变量的结构体都是Class isa,那么就必然存在继承关系。所有对象的底层都是继承objc_object,在OC中基本上所有的对象都是继承NSObject,但是真正的底层实现是objc_object的结构体类型。
对象的本质就是一个结构体,对象内的isa指向object的isa
最后再附上两张流程图,用于继续研究