zoukankan      html  css  js  c++  java
  • 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指针指向自己的父类的元类对象

    实例对象调用对象方法:通过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指针里

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

    类方法存放在元类对象中

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

  • 相关阅读:
    百度地图点击地图显示地址详情的默认方法怎么关闭,去掉百度地图api图标信息
    两个inline-block中间有空白,解决inline-block 元素之间的空白问题
    视频直播 object 标签属性详解
    2018.10.26 酷狗音乐校招前端一面经历(转)
    JS心得——判断一个对象是否为空
    echarts在tab切换时容器宽度设置为100%,只展示100px
    js生成[n,m]的随机数,js如何生成随机数,javascript随机数Math.random()
    vue2.0 日历日程表 ,可进行二次开发.
    浏览器兼容性问题解决方案 · 总结
    详解前端响应式布局、响应式图片,与自制栅格系统
  • 原文地址:https://www.cnblogs.com/huanying2000/p/13324275.html
Copyright © 2011-2022 走看看