美文网首页
iOS OC对象的本质

iOS OC对象的本质

作者: gaookey | 来源:发表于2021-11-22 17:40 被阅读0次

    实例对象

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

    • 系统分配了16个字节给 NSObject 对象(通过 malloc_size 函数获得)
    • NSObject 对象内部只使用了8个字节的空间(64bit环境下,可以通过 class_getInstanceSize 函数获得)
    创建一个实例对象,至少需要多少内存
    #import <objc/runtime.h>
    class_getInstanceSize([Student class])
    
    
    创建一个实例对象,实际上分配了多少内存
    #import <malloc/malloc.h>
    malloc_size((__bridge const void *)stu)
    

    我们平时编写的Objective-C代码,底层实现其实都是C\C++代码。所以Objective-C的面向对象都是基于C\C++的数据结构实现的。

    image.png

    Objective-C的对象、类主要是基于C\C++的 结构体 数据结构实现的。

    将Objective-C代码转换为C\C++代码:

    main.m 为源文件, main.cpp 为输出的CPP文件。

    clang -rewrite-objc main.m -o main.cpp
    

    不同平台支持的代码不同(Windows、mac、iOS)。

    iOS平台。

    • 模拟器:i386
    • 32bit:armv7
    • 64bit:arm64

    main.m 为源文件, main.cpp 为输出的CPP文件。

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

    如果需要链接其他框架,使用-framework参数。比如 -framework UIKit

    一个OC对象在内存中是如何布局的?

    NSObject 的底层实现其实是一个结构体

    @interface NSObject {
    
        Class isa;
    }
    @end
    
    struct NSObject_IMPL {
        Class isa;
    };
    

    Class 是指向结构体的指针。typedef struct objc_class *Class;。指针在64位占8个字节,32位占4个字节。

    苹果源码地址:https://opensource.apple.com/tarballs/。在 objc4/ 目录下下载源码,下载数值最大的也就是最新的。

    image.png image.png

    class_getInstanceSize 源码:

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

    instanceSize 方法中规定所有的对象至少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;
        }
    

    方法打印大小:

    NSObject *obj = [[NSObject alloc] init];
    
    // #import <objc/runtime.h>
    // 获得NSObject实例对象的成员变量所占用的大小:8
    NSLog(@"%zu", class_getInstanceSize([NSObject class]));
    
    // #import <malloc/malloc.h>
    // 获得obj指针所指向内存的大小:16
    NSLog(@"%zu", malloc_size((__bridge const void *)(obj)));
    

    也就是说创建一个 NSObject 对象会分配16个字节,但是实际利用起来的只有8个字节。

    Student 类示例

    @interface Student : NSObject
    {
        int _no;
        int _age;
    }
    @end
    
    @implementation Student
    
    @end
    

    Student 类其实是转成了 Student_IMPL

    struct NSObject_IMPL {
        Class isa;
    };
    
    struct Student_IMPL {
        struct NSObject_IMPL NSObject_IVARS; // 8个字节
        int _no; // 4个字节
        int _age; // 4个字节
    };
    
    Student *stu = [[Student alloc] init];
    
    // 16
    NSLog(@"%zu", class_getInstanceSize([Student class]));
    
    // 16
    NSLog(@"%zu", malloc_size((__bridge const void *)(stu)));
    

    Person 对象和 Student 对象占用多少内存空间

    • OC规定所有的对象至少16字节。
    • 内存对齐:结构体的大小必须是最大成员大小的倍数。
    @interface Person : NSObject
    {
        int _age;
    }
    @end
    
    @implementation Person
    
    @end
    
    @interface Student : Person
    {
        int _no;
    }
    @end
    
    @implementation Student
    
    @end
    
    struct NSObject_IMPL {
        Class isa; // 8个字节
    };
    
    struct Person_IMPL {
        struct NSObject_IMPL NSObject_IVARS; // 8个字节
        int _age; // 4个字节
    };
    
    struct Student_IMPL {
        struct Person_IMPL Person_IVARS; // 16个字节
        int _no; // 4个字节
    };
    

    Person_IMPL 占用 8+4=12 个字节,但是因为 OC规定所有的对象至少16字节 或者 内存对齐原则必须是8的倍数,所以分配16个字节。
    Student_IMPL 因为 Person_IMPL 分配16个字节,但是 Person_IMPL 实际占用了 8+4=12 个字节,所以还剩4个字节,正好装下 _no 4个字节。所以也是分配16个字节。如果说 Student 再添加一个 int _height; ,那么 malloc_size 则变为32,也就是 Person_IMPL 16的倍数...

    Person *p = [[Person alloc] init];
    // 16
    NSLog(@"%zu", class_getInstanceSize([Person class]));
    // 16
    NSLog(@"%zu", malloc_size((__bridge const void *)(p)));
    
    
    Student *stu = [[Student alloc] init];
    // 16
    NSLog(@"%zu", class_getInstanceSize([Student class]));
    // 16
    NSLog(@"%zu", malloc_size((__bridge const void *)(stu)));
    

    Person 对象占用多少内存空间

    @interface Student : NSObject
    {
        int _age;
        int _height;
        int _no;
    }
    @end
    
    @implementation Student
    
    @end 
    
    struct NSObject_IMPL {
        Class isa;
    };
     
    struct Student_IMPL {
        struct NSObject_IMPL NSObject_IVARS; // 8
        int _age; // 4
        int _height; // 4
        int _no; // 4
    }; // 结构体需要 3*8=24 个字节
    
    // 24
    NSLog(@"%zu", sizeof(struct Student_IMPL));
    
     
    Student *stu = [[Student alloc] init];
    // 24
    NSLog(@"%zu", class_getInstanceSize([Student class]));
    // 32
    NSLog(@"%zu", malloc_size((__bridge const void *)(stu)));
    

    在这里,很意外的 Studentmalloc_size 不是24,而是32。

    苹果源码地址:https://opensource.apple.com/tarballs/。在 libmalloc/ 目录下下载源码。

    其实操作系统也有自己的对齐规则。内存中有一块块分配好的内存 NANO_MAX_SIZE,大小为16的倍数,最大为256。

    Student 需要24个字节大小的时候,操作系统会把原分配好的合适的内存分配给 Student,也就是32。

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

    OC对象的分类

    Objective-C中的对象,简称OC对象,主要可以分为3种

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

    Class objc_getClass(const char *aClassName)

    • 传入字符串类名
    • 返回对应的了类对象

    Class object_getClass(id obj)

    • obj 传入的值是 instance对象,返回class对象。
    • obj 传入的值是 class对象,返回meta-class对象。
    • obj 传入的值是 meta-class对象,返回 NSObject(基类)的meta-class对象

    - (Class)class+ (Class)class

    • 返回的就是类对象

    instance对象(实例对象)

    instance对象就是通过类alloc出来的对象,每次调用alloc都会产生新的instance对象。

    NSObject *obj1 = [[NSObject alloc] init];
    NSObject *obj2 = [[NSObject alloc] init];
    
    • object1、object2是NSObject的instance对象(实例对象)
    • 它们是不同的两个对象,分别占据着两块不同的内存

    instance对象在内存中存储的信息包括:

    • isa指针
    • 其他成员变量

    instance 的 isa 指向 class。当调用对象方法时,通过 instance 的 isa 找到 class,最后找到对象方法的实现进行调用。

    class对象(类对象)

    Class objectClass1 = [obj1 class];
    Class objectClass2 = [obj2 class];
    Class objectClass3 = object_getClass(obj1);
    Class objectClass4 = object_getClass(obj2);
    Class objectClass5 = [NSObject class];
    Class objectClass6 = [[NSObject class] class];
    Class objectClass7 = [[[NSObject class] class] class];
    
    • objectClass1 ~ objectClass5都是NSObject的class对象(类对象)
    • 它们是同一个对象。每个类在内存中有且只有一个class对象

    class对象在内存中存储的信息主要包括

    • isa指针
    • superclass指针
    • 类的属性信息(@property)
    • 类的对象方法信息(instance method)
    • 类的协议信息(protocol)
    • 类的成员变量信息(ivar)
    • ......

    class 的 isa 指向 meta-class。当调用类方法时,通过 class 的 isa 找到 meta-class,最后找到类方法的实现进行调用

    meta-class对象(元类对象)

    Class objectMetaClass = object_getClass([NSObject class]);
    
    • objectMetaClass是NSObject的meta-class对象(元类对象)
    • 每个类在内存中有且只有一个meta-class对象

    meta-class对象和class对象的内存结构是一样的,但是用途不一样,在内存中存储的信息主要包括:

    • isa指针
    • superclass指针
    • 类的类方法信息(class method)
    • ......

    isa指针

    image.png

    对象的isa指针指向哪里?

    • instance对象的isa指向class对象。当调用对象方法时,通过instance的isa找到class,最后找到对象方法的实现进行调用。
    • class对象的isa指向meta-class对象。当调用类方法时,通过class的isa找到meta-class,最后找到类方法的实现进行调用。
    • meta-class对象的isa指向基类的meta-class对象
    @interface Person : NSObject
    
    @end
    
    @implementation Person
    
    @end
    
    @interface Student : Person
    
    @end
    
    @implementation Student
    
    @end 
    

    class对象的superclass指针

    当Student的instance对象要调用Person的对象方法时,会先通过isa找到Student的class,然后通过superclass找到 Person的class,最后找到对象方法的实现进行调用

    meta-class对象的superclass指针

    当Student的class要调用Person的类方法时,会先通过isa找到Student的meta-class,然后通过superclass找到Person的 meta-class,最后找到类方法的实现进行调用

    总结

    • instance的isa指向class
    • class的isa指向meta-class
    • meta-class的isa指向基类的meta-class
    • class的superclass指向父类的class。如果没有父类,superclass指针为nil
    • meta-class的superclass指向父类的meta-class。基类的meta-class的superclass指向基类的class
    • instance调用对象方法的轨迹:isa找到class,方法不存在,就通过superclass找父类。
    • class调用类方法的轨迹:isa找meta-class,方法不存在,就通过superclass找父类。

    @interface Person : NSObject
    
    @end
    
    @implementation Person
    
    @end
    
    @interface Student : Person
    
    @end
    
    @implementation Student
    
    @end
     
    struct go_objc_class {
        Class obj;
        Class superclass;
    };
    
    Person *person = [[Person alloc] init];
    Class personClass = [Person class];
    Class studentClass = [Student class];
    struct go_objc_class *pClass = (__bridge struct go_objc_class *)[Person class];
    struct go_objc_class *sClass = (__bridge struct go_objc_class *)[Student class];
    
    image.png
    # if __arm64__ 
    #     define ISA_MASK        0x0000000ffffffff8ULL
    # elif __x86_64__
    #   define ISA_MASK        0x00007ffffffffff8ULL
    # endif
    

    Person 实例对象的 isa0x011d800100008189
    Person 类对象的地址:0x0000000100008188。(0x0000000100008188 = 0x011d800100008189 & 0x00007ffffffffff8
    sClass 类对象的 superclass (sClass->superclass) 等于 Person 类对象的地址值 (pClass)。

    从64bit开始,isa需要进行一次位运算,才能计算出真实地址。

    image.png

    objc_class 的结构

    struct objc_object {
    private:
        isa_t isa;
    
        ......
    }
    
    struct objc_class : objc_object { 
        // Class ISA;
        Class superclass;
        cache_t cache;             // 方法缓存
        class_data_bits_t bits;    // 用于获取具体的类信息  
    
        class_rw_t *data() const { // 用于获取具体的类信息
            return bits.data();
        }
    
        ......
    }
    
    struct class_rw_t {
        uint32_t flags;
        uint16_t witness;
    #if SUPPORT_INDEXED_ISA
        uint16_t index;
    #endif
    
        Class firstSubclass;
        Class nextSiblingClass;
        
        const class_ro_t *ro()
        const method_array_t methods() // 方法列表
        const property_array_t properties() // 属性列表
        const protocol_array_t protocols() // 协议列表
        
        ......
    };
    
    struct class_ro_t {
        uint32_t flags;
        uint32_t instanceStart;
        uint32_t instanceSize; // instance 对象占用的内存空间
    #ifdef __LP64__
        uint32_t reserved;
    #endif
    
        union {
            const uint8_t * ivarLayout;
            Class nonMetaclass;
        };
    
        explicit_atomic<const char *> name; // 类名
        
        void *baseMethodList;
        protocol_list_t * baseProtocols;
        const ivar_list_t * ivars; // 成员变量列表
        
        ......
    };
    

    OC的类信息存放在哪里?

    • 对象方法、属性、成员变量、协议信息,存放在class对象中
    • 类方法,存放在meta-class对象中
    • 成员变量的具体值,存放在instance对象

    相关文章

      网友评论

          本文标题:iOS OC对象的本质

          本文链接:https://www.haomeiwen.com/subject/ndvbtrtx.html