runtime01 - 对象内存布局

430 阅读2分钟

Obj-C NSObject对象的内存布局是怎么样的? 占据多少内存

实际上类似一个只包含一个成员变量isa指针的结构体

struct NSObject_IMPL {
    Class isa;
}

Obj-C NSObject对象占据多少内存

有8字节就够用, 但是在alloc的时候, 系统会根据 对象的instancesize 和 16来取最大值, 所以也会申请16字节的大小

一个普通对象的内存布局是怎么样的

struct NSObject_IMPL {
    Class isa;
};

struct Person_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _age;
};

struct Student_IMPL {
    struct Person_IMPL Person_IVARS;
    long long _height;
};
@interface Person: NSObject
{
    int _age;
}
@end
@interface Student: Person
{
    long long _height;
}
@end

Demo

// xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main_arm64.cpp

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>

struct NSObject_IMPL {
    Class isa;
};

struct Person_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _age;
};

struct Student_IMPL {
    struct Person_IMPL Person_IVARS;
    long long _height;
};

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

@implementation Person
@end

@interface Student: Person
{
@public
    long long _height;
}
@end

@implementation Student
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        // allocWithZone:时分配给对象的最小空间为16, 不足16的, 也会返回16
        // 为了效率, iOS分配给对象的空间都是16的倍数
        // class_getInstanceSize 获取对象需要的最小空间
        // malloc_size           获取对象真实的占用空间

        NSObject *obj = [NSObject new];
        NSLog(@"%zu, %zu",
              class_getInstanceSize([NSObject class]), // 只有一个isa指针, 所以只需要8
              malloc_size((__bridge const void *)obj)); // allocWithZone:时分配给对象的最小空间为16, 不足16的, 也会返回16

        Person *person = [Person new];
        NSLog(@"%zu, %zu",
              class_getInstanceSize([Person class]),  // 只有一个isa指针,和一个int指针, 但是需要内存对齐, 所以需要16
              malloc_size((__bridge const void *)person)); // allocWithZone:时分配给对象的最小空间为16, 不足16的, 也会返回16

        Student *student = [Student new];
        NSLog(@"%zu, %zu",
              class_getInstanceSize([Student class]), // 因为内存对齐的原因, 需要24
              malloc_size((__bridge const void *)student)); // iOS分配给对象的空间都是16的倍数, 所以是32

        student->_height = 100;
        student->_age = 50;
        struct Student_IMPL *stu = (__bridge struct Student_IMPL *)(student);

    }
    return 0;
}


// **2021-09-27 11:09:37.980596+0800 TestOC[40849:5211544] 8, 16**

// **2021-09-27 11:09:37.980885+0800 TestOC[40849:5211544] 16, 16**

// **2021-09-27 11:09:37.980923+0800 TestOC[40849:5211544] 24, 32**


源码

static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
                              int construct_flags = OBJECT_CONSTRUCT_NONE,
                              bool cxxConstruct = true,
                              size_t *outAllocatedSize = nil)
{
    ASSERT(cls->isRealized());

    // Read class's info bits all at once for performance
    bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();
    size_t size;

// 注意一下这个方法
    size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        obj = (id)calloc(1, size);
    }
    if (slowpath(!obj)) {
        if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
            return _objc_callBadAllocHandler(cls);
        }
        return nil;
    }

    if (!zone && fast) {
        obj->initInstanceIsa(cls, hasCxxDtor);
    } else {
        // Use raw pointer isa on the assumption that they might be
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

    if (fastpath(!hasCxxCtor)) {
        return obj;
    }

    construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
    return object_cxxConstructFromClass(obj, cls, construct_flags);
}

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