要想真真切切看到一个OC对象占用多少内存, 实践是必不可少的.
初始OC对象占用内存
创建一个 Command Line Tool
工程 , 打开 main.m
在 main
函数创建一个 NSObject
.
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *objc = [[NSObject alloc] init];
}
return 0;
}
打开终端/iTerm2 , 进入到 main.m
目录. 将其转换为 c++
源码.
clang -rewrite-objc main.m -o main.cpp
文件夹目录里多出一个 main.cpp
文件 , 打开. 看到98242行代码,不要慌.我们只需要关注 NSObject
即可. 搜索 NSObject_IMPL
.
struct NSObject_IMPL {
Class isa;
};
这个就是 NSOject
对象对应的 C++ 结构体. 里面包含了一个 Class
指针. 搜索发现
typedef struct objc_class *Class;
其实就是一个指向 struct objc_class
结构体类型的指针. 那么也就是说目前我们只发现 NSObject
对象对应的结构体只包含一个 isa
指针变量 , 一个指针变量在 64 位的机器上大小是 8 个字节.
那是不是说一个 NSObject
对象就占用8个字节大小的内存呢?实际上不是的. 答案其实是: 所有的OC对象至少为16字节.
我们先来验证一下. (有兴趣的可以去看看刚刚 main.cpp
中最下面 main
函数中 对象的创建源码)
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *lbobjc = [[NSObject alloc] init];
NSLog(@"lbobjc对象实际需要的内存大小: %zd",class_getInstanceSize([lbobjc class]));
NSLog(@"lbobjc对象实际分配的内存大小: %zd",malloc_size((__bridge const void *)(lbobjc)));
}
return 0;
}
打印结果
iOS-OC对象占用内存探索[2903:181348] lbobjc对象实际需要的内存大小: 8
iOS-OC对象占用内存探索[2903:181348] lbobjc对象实际分配的内存大小: 16
先别着急猜. 我们来看下内存. 打印语句加个断点. 走你.
lldb -> po lbobjc
查看内存具体内容方法:
-
1️⃣ 打开内存查看工具:
地址栏中输入对象地址: 0x1007579c0
-
2️⃣ lldb
两种方法都表明, 目前我们创建的对象 后面几个字节全部为 00 .
我们可以通过阅读
objc4
的源码来找到答案。通过查看跟踪obj4
中alloc
和allocWithZone
两个函数的实现,会发现这个连个函数都会调用一个instanceSize
的函数:
size_t instanceSize(size_t extraBytes) {
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16bytes.
if (size < 16) size = 16;
return size;
}
上面源码中我们看出了答案, 最少会开辟16个字节. 那么为什么非要用 16 个字节来存储 8 个字节的内容呢? 这里简单解释一下 .
其实这里主要是涉及到硬件问题, 因为不同厂商之间需要一套标准化方案来解决不同厂商之间规则不同导致内存读取使用出现不统一的情况.为了解决这种问题而产生的 字节对齐.
讲到这里,我还想继续看下 当这个对象包含多个属性时使用内存情况. 以便我们彻底搞明白 OC 对象使用内存情况.
包含其他属性占用内存情况
创建一个 LBPerson
类,继承与 NSObject
, 其包含三个 int
属性
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LBPerson : NSObject
@property (nonatomic,assign) int age;
@property (nonatomic,assign) int height;
@property (nonatomic,assign) int row;
@end
回到 main
函数
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>
#import "LBPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
LBPerson * obj = [[LBPerson alloc] init];
obj.age = 4;
obj.height = 5;
obj.row = 6;
NSLog(@"lbobjc对象实际需要的内存大小: %zd",class_getInstanceSize([obj class]));
NSLog(@"lbobjc对象实际分配的内存大小: %zd",malloc_size((__bridge const void *)(obj)));
}
return 0;
}
打印结果
iOS-OC对象占用内存探索[3012:201559] lbobjc对象实际需要的内存大小: 24
iOS-OC对象占用内存探索[3012:201559] lbobjc对象实际分配的内存大小: 32
lldb查看内存
由于原本结构体 isa
指针占用8个, 然后 age
属性占用4个, height
占用 4个,此时 一组16字节刚好被占满 , row
属性再占用4个. 再次字节对齐,不足 16 补 16. 答案是 32 个字节.
继续将 部分 int
类型修改为 double
. 你会发现新的内容,篇幅原因不再讲述. 直接上结果
-
age: int , height : double , row :int
-
age: int , height : Double , row :Double
总结 (只考虑64
位):
- OC对象 最少占用
16
个字节内存.- 当对象中包含属性, 会按属性占用内存开辟空间. 每一行
16
个字节中, 剩余内存如果可以放下剩余其中一个属性 (参考倒数第二张图
) , 则会在行末存储 (注意: 并非一定是按照定义顺序来开辟空间, 放不下就开辟这样). 放不下时会重新开辟一行存储.- 最终满足
16
字节对齐标准.