iOS进阶笔记(一) 对象本质内存分析

📣 iOS进阶笔记目录


对象种类划分

  • 实例对象(instance)
  • 类对象(class)
  • 元类对象(meta-class)

分析对象内存,我们从一个栗子🌰开始

👇Student对象系统为其分配了多少内存?对象占用多少内存?有效内存(实际使用的内存大小)又是多少?

@interface Person : NSObject{
    @public
    NSString *_name;
    int _age;
}
@end

@interface Student : Person {
    @public
    NSString *_className;
    int _classNumber;
}

答案:48 40 32

下面开始说一说我是怎么计算的。

一、Student对象结构

下面是通过Clang编译的C++代码
Clang命令为 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-10.0 xxx.m

struct NSObject_IMPL {
    Class isa;
};
struct Person_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    NSString *_name;
    int _age;
};

struct Student_IMPL {
    struct Person_IMPL Person_IVARS;
    NSString *_className;
    int _classNumber;
};

根据Student对象结构,继承关系为 Student -> Person -> NSObject

1)Student对象本质是Student_IMPL结构体,里面包含了父类Person及自身成员变量_className、_classNumber;
2)Person对象内存结构为其父类NSObject对象及自身成员变量_name、_age;
3)NSObject对象本质就是objc_class类型的结构体指针isa

二、Student对象内存分析

1、首先,我们了解下结构体内存对齐规则和对象占用内存大小的约束规则

1)结构体内存对齐规则
对于结构体,编译器在编译程序时会遵循如下两条原则:(虽然带来不连续的内存碎片,却也提升了CPU快速定位成员内存地址的性能)

  • 结构体内存的偏移量大小取决于最大成员的大小;
  • 结构体大小是偏移量大小的整数倍(成员公倍数对齐)。

2)对象占用内存大小的约束规则:iOS系统规定一个对象至少占用16字节
objc-runtime-new.h文件中alignedInstanceSize函数中说明了一个对象至少占用16字节空间。

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

2、再者,结合Student对象本质及内存规则分析其所占用的内存

1)NSObject对象

struct NSObject_IMPL {
    Class isa;// isa为指针类型,占用8个字节
};

NSObject本质为NSObject_IMPL,即isa指针,所以占用8个字节

2)Person对象

struct Person_IMPL {
    struct NSObject_IMPL NSObject_IVARS;// NSObject_IMPL结构体内部就一个isa,所以占8个字节
    NSString *_name;// NSString指针类型,占8字节
    int _age;// int 占4字节
};
  • 过程分析
    Person对象本质为Person_IMPL,其内部结构中NSObject_IMPL占8个字节、_name占8个字节、_age占4字节。
    因此,有效内存为:8 + 8 + 4 = 20

    根据结构体内存对齐规则,Person_IMPL中最大成员占用内存为8个字节,因此Person_IMPL内存为8的整数倍。
    因此,Person占用内存为:3*8 = 24

    再根据对象占用内存大小的约束规则,即Person_IMPL大小为16整数倍
    因此,Person对象分配内存为:16*2 = 32

  • 代码验证

void person_size_test()
{
    Person *p = [[Person alloc] init];
    p->_name = @"小明";
    p->_age = 3;

    // 返回Person实例对象成员变量所占用的内存大小
    size_t instanceSize = class_getInstanceSize([Person class]);
    // 返回p指针所指向p对象分配内存大小
    size_t mallocSize = malloc_size((__bridge const void *)p);
    
    NSLog(@"占用内存:%zd 分配内存:%zd",instanceSize,mallocSize);// 24 32
}

由此可总结,Person对象内存分布图如下:(红色表示有效内存,蓝色表示占用内存,绿色表示分配内存)

3)Student对象

struct Student_IMPL {
    struct Person_IMPL Person_IVARS;//20 (编译器为了优化存储空间会按实际计算Person_IMPL大小:8+8+4)
    NSString *_className;// 8
    int _classNumber;// 4
};
  • 过程分析
    同理,Student_IMPL对象
    有效内存为:20 + 8 + 4 = 32
    实际占用内存:20 + 20 = 40 (_techerName和_classNumber的内存为8+4 = 12,<20,不够20,补齐20)
    分配内存为:48 (分配内存要>40 且为16的整数倍,所以是48)

  • 代码验证

void student_size_test()
{
    Student *s = [[Student alloc] init];
    s->_name = @"小明";
    s->_age = 3;
    s->_className = @"幼稚园(1)班";
    s->_classNumber = 101;
    // 返回Person实例对象成员变量所占用的内存大小
    size_t instanceSize = class_getInstanceSize([Student class]);
    // 返回p指针所指向p对象分配内存大小
    size_t mallocSize = malloc_size((__bridge const void *)s);
    
    NSLog(@"占用内存:%zd 分配内存:%zd",instanceSize,mallocSize);// 40 48
}

由此可总结,Student内存分布图如下:


扩展:class_instanceSize 和 malloc_size及sizeof() 区别
1)class_getInstanceSize为获取对象实际占用的空间(要求符合内存对齐原则)
2)malloc_size为系统为OC对象分配的空间(要求≥class_getInstanceSize,且能被16整除,iOS规定一个对象至少分配16Byte空间)
3)sizeof() :sizeof()实际上不是函数,()传入的是类型常量(int 等),在编译时确定缓冲区的长度,不能返回动态分配的内存空间大小 e.g. sizeof(int) 为 4


以上


----------End------------

posted @ 2021-08-04 20:51  ITRyan  阅读(216)  评论(0编辑  收藏  举报