面试题:一个NSObject对象占用多少内存?
一、OC对象的本质
1.1. OC的本质
我们平时编写的Objective-C代码,底层实现其实都是C\C++代码。所以Objective-C的面向对象都是基于C\C++的数据结构实现的。
思考:Objective-C的对象、类主要是基于C\C++的什么数据结构实现的?
答案:结构体,因为只有结构体才能容纳不同类型的数据。
将Objective-C代码转换为C\C++代码:
1 | xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件 |
如果需要链接其他框架,使用-framework
参数,比如-framework UIKit
。
1.2. NSObject
思考:一个OC对象在内存中是如何布局的?
创建一个NSObject对象:
1 | // main.m文件 |
NSObject在API中的定义如下,只有一个成员变量:
1 | @interface NSObject { |
把main.m
文件的代码转换为C++代码后发现,NSObject被转换成了结构体实现:
1 | struct NSObject_IMPL { |
指针在64bit环境下占用8个字节,是不是意味着创建的object对象占用8个字节呢?我们通过打印观察一下。
1 | // 获得NSObject实例对象的成员变量所占用的大小(至少需要多少内存) |
创建NSObject对象时,分配内存16个字节,实际占用8个字节。
object
指针保存的是NSObject对象内存地址,而对象的内存地址其实就是指针isa
的内存地址(因为只有一个成员变量),所以object
指针指向的是isa
的内存地址。
为什么分配了16个字节,而仅占用8个字节?这个和objc的源代码有关,苹果官方定义:在创建对象时,分配的内存至少16个字节。alignedInstanceSize
对应的objc源码是class_getInstanceSize
的实现。
NSObject对象内存布局方式:
Xcode查看内存布局:
读取内存:
1.3. 自定义对象
创建一个Student类(继承NSObject)。
1 | @interface Student : NSObject |
思考:Student对象占用多少内存?
1 | // 输出:16 |
内存分配和实际占用都是16个字节。因为Student继承自NSObject,所以也会有一个isa
指针,而Student类有两个int
类型的成员变量(int
在64bit环境占用4个字节),所以一共占用isa + _no + _age = 16
个字节。
把Student对应的类转为C++后也可以看到,Student类把NSObject的成员变量也继承过去了。
1 | struct NSObject_IMPL { |
Student对象内存布局方式:
读取内存:
1.4. 继承
创建一个Person类(继承NSObject),Student类(继承Person)。
1 | @interface Person : NSObject |
思考:一个Person对象、一个Student对象占用多少内存空间?
经过1.2和1.3的内容,基本上就可以知道Person对下和Student对象都是占用16个字节内存。
把对应代码转为C++代码:
1 | struct NSObject_IMPL { |
Person类使用class_getInstanceSize
获取到类的成员变量占用16个字节。为什么不是12?一是因为内存对齐的缘故:结构体的大小必须是最大成员变量大小的倍数。由于Person类占用内存的最大成员变量是isa
,所以Person对象占用16个字节。二是操作系统分配内存的原因:在iOS系统中,堆空间分配的内存大小是16的倍数。
class_getInstanceSize
对应的objc源码是alignedInstanceSize
:返回的是内存对齐后的大小。
Student对象为什么不是占用20个字节(Person结构体占用16个字节 + Student的成员变量_no
占用4个字节)?因为Person对象的16个字节中,最后的4个字节是空着的,而Student继承自Person,所以会把Person对象分配的最后4个字节使用_no
占用。
1.5. 属性和方法
创建一个Person类(继承NSObject)。
1 | @interface Person : NSObject |
思考:增加一个属性后,Person对象占用多少内存?
其实属性最终也会生成一个带下划线的成员变量,因此Person对象占用16个字节。
把对应代码转为C++代码:
1 | struct Person_IMPL { |
疑问:属性不是会自动生成setter和getter方法么?为什么还是占用16个字节?
因为创建对象时(alloc
)不会把方法放到对象的内存中,方法在内存中只有一份就够了。比如,创建很多个Person对象,每一个对象都有自己的age
或height
等成员变量,但是对应的setter和getter方法对于每个对象都是一样的,因此不会把方法放到对象内存中(放在方法列表中,具体介绍在后续章节会体现)。
苹果开源代码:https://opensource.apple.com/tarballs/。
objc相关的代码在objc4文件夹下,进入文件夹就可以选择下载最新版本代码(数字越大,版本越新)。
alloc相关的代码在libmalloc文件夹下。
面试题:一个NSObject对象占用多少内存?
解答:系统分配了16个字节给NSObject对象(通过malloc_size
函数获得),但NSObject对象内部只使用了8个字节的空间(64bit环境下,可以通过class_getInstanceSize
函数获得)。