探究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;
}