iOS底层原理 - OC对象的本质

212 阅读5分钟

一.OC和C_C++

1.一个NSObject对象占用多少内存?

想要知道一个NSObject对象占用多少内存空间,我们就需要知道NSObject在内存中的布局是怎样的。

我们编写的Objective-C代码,底层实现实际上都是C/C++代码

所以Objective-C的面向对象都是基于C/C++的数据结构实现的

思考: Objective-C的对象、类主要是基于C/C++的什么数据结构实现的?
答: 结构体


二.将Objective-C代码转换成C/C++代码

1.那么我们如何将OC代码转换成C/C++的代码呢?

使用终端进入到想要转换成C/C++代码的OC文件的路径

例如: cd /Users/jr/Desktop/原理-OC本质/原理-OC本质

终端输入clang命令,重写objc文件xxx.m,-o输出最,终获得C++的xxx.cpp文件

例如:clang -rewrite-objc main.m -o main.cpp

注意: 执行clang命令后,终端可能输出warning警告,无需理会即可。

查看输出C++文件我们可以看到,如下图所示OC转换成C++的代码

注意: 我们需要将生成的C++代码在iOS平台上可执行,所以,上面的clang命令不够妥当

使用xcrun命令来生成iOS平台可执行的C++文件

例如:xcrun -sdk iphones clang -arch arm64 -rewrite-objc OC源文件 -o 输出的C++文件

xcrun最终输出的C++文件大小要比直接使用clang命令生成的C++文件大小要小的多,因为该命令只生成了对应架构可执行的C++文件,省去了不可执行的部分


三.NSObject的内存本质

NSObject在OC中的定义

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}

NSObject转成C++后的代码【底层实现】【NSObject Implementation】

struct NSObject_IMPL {
    Class isa;
};

Class的定义是一个指向结构体的指针,在64位的环境下占8个字节,在32位的环境下占4个字节

typedef struct objc_class *Class;


四.class_getInstanceSize、malloc_size

通过下图可以看出,NSObject占用了16个字节的内存空间

问题: 那么class_getInstanceSize返回的是什么东西呢?

我们可以通过查看苹果开源的源码来了解Source Browser

通过查看class_getInstanceSize的实现我们可以看出,alignedInstanceSize() 函数的作用是获取类中的ivar size成员变量的大小

那么class_getInstanceSize([NSObject class]) 的作用也就是获取NSObject实例对象的成员变量的大小

总结: 一个NSObject对象占用了16字节的内存空间,其中isa成员变量占用了8个字节的内存空间,剩余8个字节被空余出来。


补充:继续查看源码探究一个NSObject对象到底是不是占用了16个字节的内存空间

通过上图可以看出,源码注释为所有的对象必须至少占用16个字节的内存空间,如果没有达到16个字节,会强制分配16个字节的内存空间给这个对象


五.窥探NSObject的内存

通过XCode的View Memory工具来查看对象的内存

由上图可以看出,系统给NSObject对象一开始分配的字节全部是00,而后,前8个字节用存放isa指针。


六.Student的本质&Student的内存布局

由上图可以看出,Student对象有三个成员变量,他们的内存地址是连续的,从10~18~C

总结: 子类结构体里面包含了父类结构体的实现再加上自己的成员变量


七.更复杂的继承结构

NSObject_IVARS占用8个字节的内存空间,_age占用四个字节的内存空间,从之前的源码我们可以知道,一个NSObject对象至少占用16个字节的内存空间,而且从内存对齐的概念来讲,我们也可以知道,结构体的大小必须是最大成员大小的倍数,也就是8 * 2

Person类继承自NSObject,包含一个成员变量_age,Student类继承自Person,包含一个成员变量_no,一个Person对象和一个Student对象所占用的内存空间均为16。

再细致一点讲,系统给一个Person对象分配了16个字节的内存空间,他的父类的结构体的实现也就是isa指针占用了8个字节的你内存空间,_age占用了4个字节的内存空间。系统给一个Student对象按照“规定”也同样分配了16个字节的内存空间,Person结构体的实现占用了Student全部的内存空间,但是Person还有4个字节的内存空间没有利用,所以把这4个字节分配给了Student的_no成员变量。

补充: 由下图部分源码可以看出,unalignedInstanceSize函数返回的是未对齐的内存大小,word_align函数是将未对齐的内存进行了对齐的操作,所以alignedInstanceSize函数返回的是对齐后的内存大小。

注意: 更多的时候我们无须关注getInstanceSize,我们只需要关注malloc_size来准确的返回一个对象所占用的内存空间的大小。

// Class's ivar size rounded up to a pointer-size boundary.
    uint32_t alignedInstanceSize() const {
        return word_align(unalignedInstanceSize());
    }

八.属性

由上图可以看出,属性类的结构体的实现中会为我们自动创建一个相应的带有下划线的成员变量。


九.内存分配注意点

创建一个实例对象,至少需要内存可以通过class_getInstanceSize函数来获得

创建一个实例对象,实际分配的内存可以通过malloc_size函数来获得

例子: 一个Person类继承自NSObject类,Person类包含3个int类型的成员变量,那么这个创建一个Person类的实例对象,至少需要8bit * 3 = 24bit的内存,但是系统实际上给Person实例对象分配了32bit的内存【个人理解,一个NSObject对象至少占用16bit,但是Person对象至少需要24bit,所以系统给Person实例对象分配了16 * 2 = 32bit的内存空间】

补充: 系统给实例对象实际分配的内存空间可以通过苹果源码中libmalloc库来查找

#define NANO_MAX_SIZE   256 /* Buckets sized {16, 32, 48, 64, 80, 96, 112, ...} */