iOS进阶笔记(一) 对象本质内存分析
对象种类划分
- 实例对象(instance)
- 类对象(class)
- 元类对象(meta-class)
分析对象内存,我们从一个栗子🌰开始
👇Student对象系统为其分配了多少内存?对象占用多少内存?有效内存(实际使用的内存大小)又是多少?
@interface Person : NSObject{
@public
NSString *_name;
int _age;
}
@end
@interface Student : Person {
@public
NSString *_className;
int _classNumber;
}
答案:48 40 32
下面开始说一说我是怎么计算的。
一、Student对象结构
下面是通过Clang编译的C++代码
Clang命令为 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-10.0 xxx.m
struct NSObject_IMPL {
Class isa;
};
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_name;
int _age;
};
struct Student_IMPL {
struct Person_IMPL Person_IVARS;
NSString *_className;
int _classNumber;
};
根据Student对象结构,继承关系为 Student -> Person -> NSObject
1)Student对象本质是Student_IMPL结构体,里面包含了父类Person及自身成员变量_className、_classNumber;
2)Person对象内存结构为其父类NSObject对象及自身成员变量_name、_age;
3)NSObject对象本质就是objc_class类型的结构体指针isa
二、Student对象内存分析
1、首先,我们了解下结构体内存对齐规则和对象占用内存大小的约束规则
1)结构体内存对齐规则
对于结构体,编译器在编译程序时会遵循如下两条原则:(虽然带来不连续的内存碎片,却也提升了CPU快速定位成员内存地址的性能)
- 结构体内存的偏移量大小取决于最大成员的大小;
- 结构体大小是偏移量大小的整数倍(成员公倍数对齐)。
2)对象占用内存大小的约束规则:iOS系统规定一个对象至少占用16字节
在objc-runtime-new.h
文件中alignedInstanceSize
函数中说明了一个对象至少占用16字节空间。
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;
}
2、再者,结合Student对象本质及内存规则分析其所占用的内存
1)NSObject对象
struct NSObject_IMPL {
Class isa;// isa为指针类型,占用8个字节
};
NSObject本质为NSObject_IMPL,即isa指针,所以占用8个字节
2)Person对象
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;// NSObject_IMPL结构体内部就一个isa,所以占8个字节
NSString *_name;// NSString指针类型,占8字节
int _age;// int 占4字节
};
-
过程分析
Person对象本质为Person_IMPL,其内部结构中NSObject_IMPL占8个字节、_name占8个字节、_age占4字节。
因此,有效内存为:8 + 8 + 4 = 20根据结构体内存对齐规则,Person_IMPL中最大成员占用内存为8个字节,因此Person_IMPL内存为8的整数倍。
因此,Person占用内存为:3*8 = 24再根据对象占用内存大小的约束规则,即Person_IMPL大小为16整数倍
因此,Person对象分配内存为:16*2 = 32 -
代码验证
void person_size_test()
{
Person *p = [[Person alloc] init];
p->_name = @"小明";
p->_age = 3;
// 返回Person实例对象成员变量所占用的内存大小
size_t instanceSize = class_getInstanceSize([Person class]);
// 返回p指针所指向p对象分配内存大小
size_t mallocSize = malloc_size((__bridge const void *)p);
NSLog(@"占用内存:%zd 分配内存:%zd",instanceSize,mallocSize);// 24 32
}
由此可总结,Person对象内存分布图如下:(红色表示有效内存,蓝色表示占用内存,绿色表示分配内存)
3)Student对象
struct Student_IMPL {
struct Person_IMPL Person_IVARS;//20 (编译器为了优化存储空间会按实际计算Person_IMPL大小:8+8+4)
NSString *_className;// 8
int _classNumber;// 4
};
-
过程分析
同理,Student_IMPL对象
有效内存为:20 + 8 + 4 = 32
实际占用内存:20 + 20 = 40 (_techerName和_classNumber的内存为8+4 = 12,<20,不够20,补齐20)
分配内存为:48 (分配内存要>40 且为16的整数倍,所以是48) -
代码验证
void student_size_test()
{
Student *s = [[Student alloc] init];
s->_name = @"小明";
s->_age = 3;
s->_className = @"幼稚园(1)班";
s->_classNumber = 101;
// 返回Person实例对象成员变量所占用的内存大小
size_t instanceSize = class_getInstanceSize([Student class]);
// 返回p指针所指向p对象分配内存大小
size_t mallocSize = malloc_size((__bridge const void *)s);
NSLog(@"占用内存:%zd 分配内存:%zd",instanceSize,mallocSize);// 40 48
}
由此可总结,Student内存分布图如下:
扩展:class_instanceSize 和 malloc_size及sizeof() 区别
1)class_getInstanceSize为获取对象实际占用的空间(要求符合内存对齐原则)
2)malloc_size为系统为OC对象分配的空间(要求≥class_getInstanceSize,且能被16整除,iOS规定一个对象至少分配16Byte空间)
3)sizeof() :sizeof()实际上不是函数,()传入的是类型常量(int 等),在编译时确定缓冲区的长度,不能返回动态分配的内存空间大小 e.g. sizeof(int) 为 4
以上
----------End------------
(限于水平,本文可能存在瑕疵甚至错误的地方。如有发现,还请留言指正,相互学习。thx! )
KEEP LOOKING, DON`T SETTLE!