Objective-C对象内存占用计算

285 阅读2分钟

64位系统各个类型的内存占用情况

我们需要清楚各个基本类型占用情况,值得注意的是:64位 系统的指针是 64位,即 8字节

类型内存占用情况(字节Byte)
char1
int4
float4
long8
double8
指针8

内存对齐

  • 对象的内存占用并不是其成员变量的简单总和🙅
  • iOS 中,会发生两次内存对齐,即 最终内存占用 >= 成员变量的总和 🙆
  • 第一次内存对齐发生在 instanceSize 中,计算成员变量的内存大小,并限制最小内存为 16字节
// runtime库代码 instanceSize方法
inline size_t instanceSize(size_t extraBytes) const {
        if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
            return cache.fastInstanceSize(extraBytes);
        }

        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        if (size < 16) size = 16;
        return size;
    }
  • 第二次内存对齐发生在 calloc 中,即申请内存的时候会在 iOSBuckets sized 内存桶申请一块指定大小的内存
// malloc库代码 calloc方法
// count:内存个数,默认1
// size:成员变量的内存大小,拿第一次对齐后的数据
calloc(count, size);

// calloc方法会在iOS内存桶内申请内存
// iOS内存桶,会指定大小,规则为16的倍数,上限为256
// 例如24则申请32
Buckets sized {16, 32, 48, 64, 80, 96, 112, ..., 256}

NSObject对象内存计算

  • 根据 OCC++ 的代码,我们可以看到 NSObject 的结构体是这样的,包含一个 ISA 指针
  • 成员变量 ISA 的占用为 8字节 ,但是 NSObject 的内存占用却是 16字节,是因为发生了两次内存对齐
// NSObject_IMPL内部存放ISA指针
struct NSObject_IMPL {
Class isa; // 8个字节
};

其他对象内存计算

  • 根据 OCC++ 的代码,我们可以看到 People 的结构体是这样的,包含一个 ISA 指针,一个 int 类型
  • 成员变量 ISA 的占用为 8字节int 类型的占用为 4字节 但是 People 的内存占用却是 16字节,也是是因为发生了两次内存对齐
  • 如果是使用 @property,除了添加成员变量,还会重写 get/set方法get/set方法 会存放在类对象方法里面,而不是放在实例对象内部
// OC源码
@interface People : NSObject {
    @public
    int _count;
}
@end

// 转为C++,添加_IMPL后缀,生成对应结构体
struct People_IMPL {
struct NSObject_IMPL NSObject_IVARS; // 8个字节
int _count; // 4个字节
};

// NSObject_IMPL内部存放ISA指针
struct NSObject_IMPL {
Class isa; // 8个字节
};

架构图.png

附录:查看内存的方法

方法查看内存

    #import <objc/runtime.h>
    // 方法一
    // 获取实例对象的大小,不推荐使用,有误差
    // 输入:Class类
    // 返回:成员变量所占空间大小,且会发生内存对齐
    // 对齐规则:最大成员变量的倍数
    // 例子输出结果为8
    NSLog(@"%zd",class_getInstanceSize([NSObject class]));

    #import <malloc/malloc.h>
    // 方法二🌟
    // C语言函数,需要__bridge桥接,推荐使用
    // 输入:实例对象
    // 返回:指针所指内存大小
    // 例子输出结果为 16
    NSLog(@"%zd",malloc_size((__bridge const void *)([[NSObject alloc] init])));

    // 方法三
    // 宏定义类似的获取内存方法
    // 输入:基本数据类型或者结构体
    // 输出:计算类型或表达式在内存中的大小
    // 在编译时期计算的,而不是在运行时期
    // 它仅用于计算类型或表达式在内存中的大小,并不会对其进行实际的计算或执行
    NSLog(@"%zd",sizeof(int));

Xcode查看内存工具

image.png

image.png

  • 访问对象地址,该对象内存为 16字节,每个数字代表 16进制1个字节
  • 前八个字节存放 TestClassISA指针地址,后八个字节存放 int类型属性,明显 int类型属性 的值为 5
  • iOS 内存阅读方式为大端(big-endian),即高地址往低地址读取,当然也有另外一种相反的阅读方式则为小端(little-endian

LLDB查看内存

  • p test 输出地址
  • po test 输出对象
  • x test 输出对象内存

image.png