OC对象的本质

一个NSObject对象占用多少内存

我们都知道 我们平时编写的OC代码 底层实现其实是C/C++,然后编译器再把C/C++代码转换为汇编语言代码,汇编代码最终会变成机器语言。

所以OC的面向对象都是基于C/C++的数据结构来实现的。

那么我们OC中的对象 类 都是基于C/C++什么样的数据结构实现的呢?

我们的答案就是 根据C/C++中的结构体来实现的。

将OC代码转换为C/C++代码?

通过下面的操作 我们可以看到mian函数转换为C/C++代码

Last login: Thu Jul 16 16:55:54 on ttys002
farben@FarbendeMacBook-Pro ~ % cd /Users/farben/Desktop/底层/OC对象的本质/NSObject对象占用内存/NSObject对象占用内存
farben@FarbendeMacBook-Pro NSObject对象占用内存 % ls
main.m
farben@FarbendeMacBook-Pro NSObject对象占用内存 % clang -rewrite-objc main.m -o main.cpp

转换后的mian.m函数为

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));

    }
    return 0;
}

但是这句命令 clang -rewrite-objc main.m -o main.cpp 并没有指定编译C/C++代码的平台,实际上iOS windows MacOS不同平台上被编译成的C/C++代码是不相同的。所以我们可以指定一下编译平台

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 'oc源文件' -o '输出的cpp文件'

#import <Foundation/Foundation.h>

//编译为mainarm64.cpp后 生成的结构体
//即为 NSObject_implementation 就是NSObject的底层实现
struct NSObject_IMPL {
    Class isa;
};
//我们直接通过NSObject 点进去可以看到OC中是这样定义NSobject的
//可以看到 和C/C++的结构体实现大概差不多 可以证明我们OC中的类 底层是通过
//C/C++的结构体来实现的
/*
@interface NSObject <NSObject> {
    Class isa;
}
@end
 */

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

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

NSObject的底层实现

@interface NSObject <NSObject> {
    Class isa;
}
@end

转换为C/C++后是这样的

struct NSObject_IMPL {
    Class isa;
};

可以知道NSObject 这个对象在内存中就是一个结构体  那么isa是什么东西呢,我们点进去可以发现是一个指向结构体的指针 既然是个指针 那么在64位占有8个字节 32为4个字节

typedef struct objc_class *Class;

就是一个指向结构体的指针,意思就是指向这个类的指针。所以isa是个指针,那么在64位环境下占8个字节 在32位环境下占4个字节。

那么如果我们写了这句代码

NSObject *obj = [[NSObject alloc] init];

首先 alloc 分配存储空间给NSObject这个结构体  空间里面只有一个isa  isa这个指针指向的地址 就是刚刚分配的地址 obj这个指针也是指向这个地址

但是在实际中 我们使用运行时获取一个类的大小的时候class_getInstanceSize([NSObject class])获取NSObject类实例对象的内存对齐后的成员变量所占的大小 返回8个字节 但是使用malloc_size((__bridge const void *)obj) 获取的obj所指向内存的带下 返回的是16个字节 所以声明一个类对象 系统所分配的内存空间是16个字节

一个NSObject对象占有多少内存

系统会分配16个字节给NSObject对象,但是在64位环境下NSObject只使用了8个字节。在alloc分配空间的时候 其实是调用allocWithZone 其内部 如果一个对象的成员变量小于16个字节的时候,内存会强制分配16个字节的空间。也就是说一个OC对象最小占用的内存空间是16个字节

class_getInstanceSize([NSObject class]) 返回内存对齐后的所有的成员变量所占的内存空间 (内存对齐 所占字节最大的成员变量占有内存的倍数)也就是一个成员变量最少需要的内存大小

malloc_size((__bridge const void *)obj) 创建一个实例对象 实际分配的空间 至少是16字节 也有内存对齐的概念 16的倍数

OC对象可以分为 实例对象 类对象 元类对象

实例对象:内存中存储着自己的成员变量(成员变量的赋值信息) 包括(isa指针)

类对象:一个类的类对象在内存中只有一份

类对象在内存中存放着isa指针 superClass指针 类的属性信息 类的对象方法信息 类的协议信息 类的成员变量信息(成员变量的描述信息 类型 名字等)

元类对象:获取方法 Class mataClass = object_getClass([NSObject class]);(传递的参数必须是类对象)

元类对象:存储的是类方法信息 isa指针 superClas指针  每个类有且只有一个元类对象

 

实例对象的isa指针指向自己的类对象(class) 类对象的isa指针指向元类对象

当调用对象方法时 通过实例对象的isa指针找到Class 最后找到其存储的对象方法进行实现

当调用类方法时 通过类对象的isa指针指向元类对象 最后找到其存储的类方法进行实现

superClass其实就是指向自己的父类 假如一个子类对象调用了父类的方法 那么流程是这样的子类对象通过isa自己的类对象 然后自己的类对象通过superClass找到父类对象  然后父类对象对象在从自己存储的对象方法中找到实现

每个类都有自己的元类对象 元类对象也包含着isa指针 superClass指针 那么元类对象怎么理解呢?

类对象的superClass指针 指向父类的类对象 元类对象的superClass指针指向父类的元类对象中

那么如果我们调用一个类方法 其实流程是这样的 先通过自己的isa指针找到自己的元类对象 然后调用元类对象保存的类方法来实现

我们可以总结一下:

实例对象的isa指针 指向自己的类对象 类对象的isa指针自己的指向元类对象 类对象的superClass指针指向自己的父类类对象 如果没有父类superClass就为空nil 元类的superClass指针指向自己的父类的元类对象 但是基类的元类对象的supreClass指针指向了自己的类对象

实例对象调用对象方法:通过isa找到自己的类对象 然后在类对象存储的对象方法中找到方法实现

实例对象调用父类的对象方法:通过isa指针找到自己的类对象 类对象通过自己的superClass指针找到他的父类对象 在父类对象存储的对象方法中找到方法实现

类对象调用自己的类方法: 类对象通过自己的isa指针找到自己的元类对象 然后在元类对象存储的类方法中找到方法实现

类对象调用自己父类的类方法: 通过自己的isa指针找到自己的元类对象 元类对象通过superClass指针找到父类的元类对象 然后在父类元类对象存储的类方法中找到方法实现

那么现在只有一个问题了 元类对象的isa指向哪里呢?

其实在元类对象的isa是指向基类的元类对象 在OC中我们可以认为基类是NSObject 值得注意的是基类的元类对象的superClass指针指向了基类的类对象

OC中类的构成信息探究

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

OC1.0的构成是以上信息 和我们预想的差不多 包含了isa指针 superClass指针 类名 版本 大小 成员变量 方法列表等信息 但是现在是OC2.0了 最新的信息构成如下

struct objc_class {
    Class isa;
    Class superclass;
    cache_t cache;             //方法缓存
    class_data_bits_t bits; //用户获取类的具体信息
}

bits.data()可以获取类的具体信息 struct class_rw_t

//可读写的表 
class_rw_t *data() const 
    return bits.data();
}
class_rw_t* data() const {
    return (class_rw_t *)(bits & FAST_DATA_MASK);
}
struct class_rw_t {
    uint32_t flags;
    uint32_t version;
    const class_ro_t *ro;
    method_list_t * methods;    // 方法列表
    property_list_t *properties;    // 属性列表
    const protocol_list_t * protocols;  // 协议列表
    Class firstSubclass;
    Class nextSiblingClass;
    char *demangledName;
};

class_ro_t 是这样定义的

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;  // instance对象占用的内存空间
#ifdef __LP64__
    uint32_t reserved;
#endif
    const uint8_t * ivarLayout;
    const char * name;  // 类名
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;  // 成员变量列表
    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
};

对象的isa指针指向哪里?

实例对象的isa指针指向自己的class对象

类对象的isa指针指向元类对象

元类对象的isa指针指向基类(NSObejct)的元类对象

 

OC的类信息存放在哪里?

OC的类信息 包含属性信息 协议信息 方法信息 成员变量信息 父类信息

父类信息包含在类对象的superClass指针里

对象方法 属性 成员变量 协议信息存放在类对象中

类方法存放在元类对象中

成员变量的具体值存放在实例对象里面

 

posted @ 2020-11-05 17:01  幻影-2000  阅读(167)  评论(0编辑  收藏  举报