本文主要讲述了OC对象的本质
面试题引发的思考:
Q: 一个NSObject
对象占用多少内存?
#import
#import
#import
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [[NSObject alloc] init];
// 创建一个实例对象,实际上分配多少内存 - 16
NSLog(@"%zd", malloc_size((__bridge const void *)obj));
// 创建一个实例对象,至少需要多少内存 - 8
NSLog(@"%zd", class_getInstanceSize([NSObject class]));
}
return 0;
}
系统分配了16个字节给
NSObject
对象(通过malloc_size
函数获得);
OC源码显示,分配给对象的内存为16个字节的倍数,最少为16个字节。NSObject
对象内部使用了8个字节的空间(64bit环境下,通过class_getInstanceSize
函数获得)。
1. 分析:
- 我们平时编写的OC代码,底层实现其实都是C\C++代码;
- OC的面向对象都是基于C\C++的数据结构实现的。
Q: OC的对象、类主要是基于C\C++的什么数据结构实现的呢?
- 结构体。
将OC代码转换为C\C++代码,使用xcrun:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件
OC生成NSObject
对象:
NSObject *objc = [[NSObject alloc] init];
在main-arm64.cpp中搜索NSObject_IMPL
(IMPL
即implementation
实现):
由此看出:OC的对象是基于C的结构体实现的。
Q: 那么这个结构体占用多少内存呢?
- 此结构体只有一个成员:
isa
指针,而指针在64位架构中占8个字节;
即:一个NSObject
对象占用的内存是8个字节。
2. 深入:
Q: 那么一个自定义对象占用多少内存?
实现一个自定义类:
@interface Student : NSObject{
@public
int _no;
int _age;
}
@end
@implementation Student
int main(int argc, const char * argv[]) {
@autoreleasepool {
Student *stu = [[Student alloc] init];
stu -> _no = 4;
stu -> _age = 5;
NSLog(@"%@",stu);
}
return 0;
}
@end
同上述步骤生成C++文件,在文件中找出Student_IMPL
:
Student_IMPL
中第一个参数是NSObject_IMPL
的实现,而NSObject_IMPL
内部是Class isa
;
即struct NSObject_IMPL NSObject_IVARS
等同于Class isa
,那么可转化为下图代码:
- 此结构体占用的存储空间为:
isa
指针(指针8个字节) +_no
(int4个字节) +_age
(int4个字节) 共16个字节空间,即Sutdent
对象占用16个字节的存储空间。
假设stu
的地址为0x100400110,Sutdent
对象的变量则分别对应以下的地址:
验证一下:
@implementation Student
int main(int argc, const char * argv[]) {
@autoreleasepool {
Student *stu = [[Student alloc] init];
// 赋值
stu -> _no = 4;
stu -> _age = 5;
// 强制转化
struct Student_IMPL *stuImpl = (__bridge struct Student_IMPL *)stu;
// 打印出 _no = 4, _age = 5
NSLog(@"_no = %d, _age = %d", stuImpl->_no, stuImpl->_age);
}
return 0;
}
@end
先给Student
对象赋值_no = 4, _age = 5
,那么它的内存中也存储_no = 4, _age = 5
,然后将OC对象强制转换为Student_IMPL
结构体,通过结构体的指针访问_no
、_age
,输出结果也是_no = 4, _age = 5
,说明stu
这个指针它指向的对象,是结构体,证明了我们的猜想。
3. 延伸:
Q: 以下一个Person
对象,一个Student
对象占用多少内存空间,即打印结果是什么?
// TODO: ----------------- Person类 -----------------
@interface Person : NSObject {
int _age;
}
@end
@implementation Person
@end
// TODO: ----------------- Student类 -----------------
@interface Student : Person {
int _no;
}
@end
@implementation Student
@end
// TODO: ----------------- main -----------------
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"Person - %zd Student - %zd",
class_getInstanceSize([Person class]),
class_getInstanceSize([Student class])
);
}
return 0;
}
根据上文可以得出下面的关系图:
Q: 那么上面的打印结果是多少呢?
-
Person - 16 Student - 16
。
Q: 奇怪了,不应该打印Person - 12 Student - 16
吗?
Person
对象确实只使用12个字节,但是由于内存对齐(结构体的大小必须是最大成员大小的倍数)的原因,所以Person
对象占用了16个字节;
即isa
指针(指针8个字节)的倍数。而OC源码显示,分配给对象的内存为16个字节的倍数,最少为16个字节。
4. 更多:
Q: 以下代码打印结果是什么?
@interface Person : NSObject {
int _age;
int _height;
int _no;
}
@end
@implementation Person
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
// 24 32
NSLog(@"%zd %zd",
class_getInstanceSize([Person class]),
malloc_size((__bridge const void *)(p)));
}
return 0;
}