iOS OC对象的本质

2021-11-22  本文已影响0人  gaookey

实例对象

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

创建一个实例对象,至少需要多少内存
#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平台。

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 对象占用多少内存空间

@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种

Class objc_getClass(const char *aClassName)

Class object_getClass(id obj)

- (Class)class+ (Class)class

instance对象(实例对象)

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

NSObject *obj1 = [[NSObject alloc] init];
NSObject *obj2 = [[NSObject alloc] init];

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

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

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

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

meta-class对象(元类对象)

Class objectMetaClass = object_getClass([NSObject class]);

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

isa指针

image.png

对象的isa指针指向哪里?

@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,最后找到类方法的实现进行调用

总结


@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的类信息存放在哪里?

上一篇 下一篇

猜你喜欢

热点阅读