问题:NSObject对象占用内存空间大小为多少字节?


补充: 一个字节由8个二进制位组成.

示例代码如下所示.

int main(int arg1, const char* argv[]) {
    NSObject *obj = [[NSObject alloc] init];
    return 0;
}

转义为Arm64位的源码指令如下所示.

xcrun -sdk iphoneos -arch arm64 -rewrite-objc 源文件路径 -o 目标文件路径

iOS对象的本质是C/C++的结构体,通过转义或者查看我们得知iOS对象结构体中有且只有一个成员变量,那就是isa指针.所以isa指针内存地址即为iOS对象结构体的地址.

struct NSObject_IMPL {
    Class isa;
};

那么一个NSObject对象到底占用了多少内存呢.

我们通过引入 <objc/runtime.h> 使用如下方法发现,字节数是 8个字节.

class_getInstanceSize([NSObject class]);

但是我们通过引入 <malloc/malloc.h> 使用如下方法,字节数竟然是 16个字节.

malloc_size(const void *ptr);

// 使用方式如下
malloc_size((__bridge const void *)obj);

这时候我们就可以去官方查看一下具体的源码,然后下载最新的objc4中的源码了,开源源码地址如下所示.

opensource.apple.com/tarballs

结论

通过阅读代码发现, class_getInstanceSize 是返回成员变量的大小,并非结构体本身的大小,NSObject成员变量有且只有一个isa指针,所以只有8个字节.

size_t class_getInstanceSize(Class cls) {
    if (!cls) return 0;
    ruturn cls->alignedInstanceSize();
}
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() {
    ruturn word_align(unalignedInstanceSize());
}

那么为什么NSObject对象创建时会有16个字节呢? 调用流程如下所示.

PS: 基于 objc4-818.2.tar.gz 版本

id _objc_rootAllocWithZone(Class cls, malloc_zone_t *zone) {
    id obj;

    if (fastpath(!zone)) {
        obj = class_createInstance(cls, 0);
    } else {
        obj = class_createInstanceFromZone(cls, 0, zone);
    }

    if (slowpath(!obj)) obj = _objc_callBadAllocHandler(cls);
    return obj;
}
id (* zoneAlloc)(Class, size_t, void *) = _class_createInstanceFromZone;

id class_createInstanceFromZone(Class cls, size_t extraBytes, void *z) {
    OBJC_WARN_DEPRECATED;
    return (*zoneAlloc)(cls, extraBytes, z);
}
id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone) {
    void *bytes;
    size_t size;

    // Can't create something for nothing
    if (!cls) return nil;

    // Allocate and initialize
    size = cls->alignedInstanceSize() + extraBytes;

    // CF requires all objects be at least 16 bytes.
    if (size < 16) size = 16;

    if (zone) {
        bytes = malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        bytes = calloc(1, size);
    }

    return objc_constructInstance(cls, bytes);
}

通过 _class_createInstanceFromZone 方法,我们可以明确了解当 size < 16 时,内存分配大小为16个字节,而size的计算是通过 alignedInstanceSize 获取成员变量大小 再加上 外部extraBytes的大小而生成的,NSObject成员变量有且只有一个isa指针为8个字节,所以计算出来的size大小为8字节(小于16字节),因此最终的字节数为16字节.

所以一个NSObject对象占用的内容大小为16个字节.


问题: 一个普通类创建时需要开辟多少内存空间呢?


操作系统数据类型字节数
32位char1个字节
64位char1个字节
32位char *p (指针)4个字节
64位char *p (指针)8个字节
32位int4个字节
64位int4个字节
32位unsigned int4个字节
64位unsigned int4个字节
32位double8个字节
64位double8个字节
32位long4个字节
64位long8个字节
32位long long8个字节
64位long long8个字节
32位BOOL1个字节
64位BOOL1个字节
32位float4个字节
64位float4个字节
32位NSInteger(=int)4个字节
64位NSInteger(=long)8个字节
32位CGFloat(=float)4个字节
64位CGFloat(=double)8个字节
32位CGSize8个字节
64位CGSize16个字节
32位CGRect16个字节
64位CGRect32个字节
32位short2个字节
32位unsigned long4个字节

在不同的位操作系统下,只有指针数据类型所占用空间是不同的,所以一个普通类的所占用的内存空间就是 isa指针 + 所有成员变量的所占用的内存空间.然后根据内存对齐法则,即可算出成员变量所占用的内存空间大小.

结构体内存对齐法则: 结构体的开辟内存大小必须是最大成员所占字节的倍数.

然后,调用开辟内存方法 calloc 如果总内存大小满足 16字节(iOS系统最小开辟空间大小规范) 的倍数,那么就直接开辟,否则就需要按需新增内存空间.这有点类似于结构体内存对齐原则.

libmalloc 源码可以查询到开辟空间规范限制,如下所示.

#define NANO_MAX_SIZE 256 /* Buckets sized {16, 32, 48, ...., 256}*/

例如 如果一个继承于NSObject的类有且只有一个CGRect属性,由于CGRect是结构体,最终由 4个 CGFloat类型构成,那么就是32个字节,再加上isa指针的8个字节,由于都是8字节数据类型,进行内存对齐法则之后,依然是8个字节,故最终占用40个字节,然后还要根据是16字节的倍数,所以这个对象最终开辟48个字节.


IT界无底坑洞栋主 欢迎加Q骚扰:676758285