前言

在大学时期学习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分配的内存大小。

通过上述博客可知:Runtimeobj对象分配了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;
}