探究Objective-C对象的内存模型(一):实例对象
前言
在大学时期学习C时,为了深入了解C的机制及实现原理,老师推荐了本《深度探索C++对象模型》,草率读完一遍后对当时的我来说收获巨大,一度自信心膨胀…
然而在学习OC时,相关的深入的书籍有些缺乏,只能通过零零散散的博客了解个大概。
因此,本系列文章通过查阅OC的Runtime源码+动态调试的方法来梳理一下OC中对象的内存模型。
一、NSObject实例对象内存模型
为了解NSObject实例对象的内存模型,首先需要创建一个实例对象。
a - 创建NSObject实例对象
创建实例对象代码如下:
NSObject *obj = [[NSObject alloc] init];
要想知道obj所指向的对象在内存中是怎样存储的,需要知道两个东西:
obj指针所指向对象的地址。obj指针所指向对象的大小。
b - 获取实例对象地址
地址,很容易获得:
NSLog(@"obj -> %p",obj);
c - 获取实例对象大小
当我想用以下代码获得对象的大小时,出现了问题:
NSLog(@"obj size = %d",sizeof(NSObject));
编译报错:Application of 'sizeof' to interface 'NSObject' is not supported on this architecture and platform
在《NSObject 底层本质》博客中提供了三种思路:
- 方式一:通过
clang -rewrite-objc main.m -o main.cpp命令,把OC代码转换为C代码,在C代码中查看NSObject的底层实现。 - 方式二:通过
class_getInstanceSize([NSObject class])获取NSObject对象实例所占用的内存大小。 - 方式三:通过
malloc_size((__bridge const void *)obj)来获取系统为obj分配的内存大小。
通过上述博客可知:Runtime为obj对象分配了16字节的内存,但obj对象实际只会占用8字节,这8个字节主要用来存放isa指针相关的值。
d - obj对象的本质
同样,通过上述博客可知,NSObject对象其本质就是下面代码中的结构体:
struct MY_NSObject_IMPL {
Class isa;
};
彩蛋问题一:如何在栈内存中创建一个NSObject的对象?
那么,问题来了,我来提出一个与C++的一些黑魔法问题类似的问题:如何在栈内存中创建一个NSObject的对象呢?(答案见文末)
e - obj对象内存模型(图)
假设obj指针所指向对象的内存地址为0x100611690,那么obj对象的内存模型如下:

二、单继承
a - 创建Person类(继承自NSObject)
创建一个Person类继承于NSObject类,为了降低分析的复杂度,采用基本数据类型做为成员变量,代码如下:
@interface Person : NSObject {
@public
int _age;
}
@end
@implementation Person
@end
b - 创建Person类的实例对象
Person * p = [[Person alloc] init];
p->_age = 0x12345678;
c - p的本质
struct My_Person_IMPL {
struct My_NSObject_IMPL NSObject_IVARS;
int _age;
};
彩蛋问题2:如何在栈内存中创建一个Person的对象?
d - p的内存模型
假设p指针所指向对象的内存地址为0x100611690,那么p对象的内存模型如下:

e - 创建Student类(继承自Person)
@interface Student : Person{
@public
int _no;
}
@end
@implementation Student
- (NSString*)description {
return [NSString stringWithFormat:@"Student::my age is %d, my no is %d",self->_age,self->_no];
}
@end
f - 创建Student类的实例对象
Student *s = [[Student alloc] init];
s->_age = 0x11111111;
s->_no = 0x22222222;
g - s的本质
struct My_Student_IMPL {
struct My_Person_IMPL Person_IVARS;
int _no;
};
彩蛋问题3:如何在栈内存中创建一个Student的对象?
h - s的内存模型
假设s指针所指向对象的内存地址为0x100611690,那么s对象的内存模型如下:

三、多继承
什么?
你看到了这里?
你还想了解Objective-C的多继承?
看来你的基础不够牢固呀。
大声跟我念三遍:
Objective-C不支持多继承!
Objective-C不支持多继承!
Objective-C不支持多继承!
四、菱形继承
啥?
你咋又来了?
不是已经告诉你了Objective-C不支持多继承吗?
那肯定更不会存在菱形继承了!!!
五、彩蛋问题答案
在实现My_Person_IMPL/My_Student_IMPL的时候,记得一定要设置内存对齐的参数为4,只有这样,才能正确将内存的里值取出来。
struct My_NSObject_IMPL {
Class isa ;
};
#pragma push
#pragma pack(4)
struct My_Person_IMPL {
struct My_NSObject_IMPL NSObject_IVARS;
int _age;
};
struct My_Student_IMPL {
struct My_Person_IMPL Person_IVARS;
int _no;
};
#pragma pop
int main(int argc, const char * argv[]) {
struct My_NSObject_IMPL myObjOnStack = {[NSObject class]};
NSObject *objOnStack = (__bridge NSObject*)&myObjOnStack;
NSLog(@"objOnStack = %@",[objOnStack description]);
struct My_Person_IMPL myPOnStack = {{[Person class]},10};
Person *pOnStack = (__bridge Person*)&myPOnStack;
NSLog(@"pOnStack = %@",[pOnStack description]);
struct My_Student_IMPL mySOnStack = {{{[Student class]},20},30};
Student * sOnStack = (__bridge Student*)&mySOnStack;
NSLog(@"sOnStack = %@",[sOnStack description]);
return 0;
}
输出如下:
2021-07-20 21:54:23.867409+0800 testCommand[2028:56637] objOnStack = <NSObject: 0x7ffeefbff4f8>
2021-07-20 21:54:23.867982+0800 testCommand[2028:56637] pOnStack = Person::my age is 10
2021-07-20 21:54:23.868046+0800 testCommand[2028:56637] sOnStack = Student::my age is 20, my no is 30
六、小结
本文小结如下:
- 实例对象的前8个字节存储了
isa指针,剩余的内存用来存储成员变量。 - OC对象单继承的内存模型与C++类似,在父类对象基础上(不严谨),添加自己的成员变量。
七、新问题
问题一:isa指针所指向的内存里都存储了哪些东西?
问题二:下面代码输出结果,为什么与预期不符?
int main(int argc, const char * argv[]) {
//struct My_Student_IMPL mySOnStack = {{{[Student class]},20},30};
struct My_Student_IMPL mySOnStack = {{{[Person class]},20},30};
Student * sOnStack = (__bridge Student*)&mySOnStack;
NSLog(@"sOnStack = %@",[sOnStack description]);
return 0;
}