面试题引发的思考:
Q: 一个NSObject
对象占用多少内存?
// TODO: ----------------- main -----------------
#import <Foundation/Foundation.h>
#import <malloc/malloc.h>
#import <objc/runtime.h>
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
函数获得)。
Q: OC的对象、类主要是基于C\C++的什么数据结构实现的呢?
- 结构体。
0. 新版本iOS项目设置为MRC运行崩溃解决办法
Xcode -> Targets --> Build Settings --> 搜索“automatic reference counting”,MRC模式设置为“NO”,ARC模式设置为“YES”。
修改main
函数:
1. OC底层实现
- 我们平时编写的OC代码,底层实现其实都是C\C++代码;
- OC的面向对象都是基于C\C++的数据结构实现的。
将OC代码转换为C\C++代码,使用xcrun:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件
OC生成NSObject
对象:
NSObject *obj = [[NSObject alloc] init];
在main-arm64.cpp中搜索NSObject_IMPL
(IMPL
即implementation
实现):
由OC源码可知:
- OC的对象是基于C的结构体实现的;此结构体只有一个成员:
isa
指针,而指针在64位架构中占8个字节;- 即:一个
NSObject
对象占用的内存是8个字节。
2. 自定义对象占用内存
实现一个自定义类:
// TODO: ----------------- Student类 -----------------
@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;
}
return 0;
}
@end
同上述步骤生成C++文件,在文件中找出Student_IMPL
:
结构体Student_IMPL
中第一个参数是结构体NSObject_IMPL
,而NSObject_IMPL
内部是Class isa
;
即struct NSObject_IMPL NSObject_IVARS
等同于Class isa
,那么可转化为下图代码:
此结构体占用的存储空间为:isa
指针(指针8个字节) + _no
(int
4个字节) + _age
(int4
个字节) 共16个字节空间;
即Sutdent
对象占用16个字节的存储空间。
假设stu
的地址为0x100400110,Sutdent
对象的变量则分别对应以下的地址:
验证一下:
// TODO: ----------------- Student类 -----------------
@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;
// 强制转化
struct Student_IMPL *stuImpl = (__bridge struct Student_IMPL *)stu;
NSLog(@"_no = %d, _age = %d", stuImpl->_no, stuImpl->_age);
}
return 0;
}
@end
// 打印结果
Demo[1234:567890] _no = 4, _age = 5
先给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;
}
// 打印结果
Demo[1234:567890] 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];
NSLog(@"%zd %zd",
class_getInstanceSize([Person class]),
malloc_size((__bridge const void *)(p)));
}
return 0;
}
// 打印结果
Demo[1234:567890] 24 32