美文网首页
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