美文网首页
OC对象的本质

OC对象的本质

作者: RainyHand | 来源:发表于2020-05-06 18:49 被阅读0次

    总结性文章,如有问题,请评论

    image.png

    从以下几个维度分析

    1.对象的分类
    • 首先按分类来说,OC对象分为实例对象、类对象、元类对象三种。
      他们的底层实现都是结构体.
    2.内部实现
    • 实例对象
    @interface NSObject <NSObject> {
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wobjc-interface-ivars"
        Class isa  OBJC_ISA_AVAILABILITY;
    #pragma clang diagnostic pop
    }
    
    Class的内部实现
    typedef struct objc_class *Class; 
    
    所以ISA是一个指向结构体的指针变量
    

    当继承发生时

    struct NSobject_imp{
            Class imp;
        };
        
        struct Person {
            struct NSobject_imp imp_isa;
            int _age;
            int _num;
            int _on;
        };
    通俗理解,生成的person实例对象中,包含三个ivar + NSObject的ISA指针
    
    

    实例对象的结构如下:

    • ISA //指向父类的指针
    • _ivar //成员变量
    • _ivar //成员变量
    • _ivar //成员变量

    类对象class
    通过xcode查看的类对象已经过时,下边是之前的定义,最新的定义可以查看objc的源码,在下边列出几个重要的点

    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;
    

    新版

    struct objc_class : objc_object {
        // Class ISA;
        Class superclass;
        cache_t cache;             // formerly cache pointer and vtable
        class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    最新版的ISA被注释掉了,不要被迷惑,只要是对象,就会有ISA,实际是objc_class继承于objc_object
    而...
    struct objc_object {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    };
    
    然后展开class_data_bits_t里边public的 class_rw_t, 看通过 rw可以了解,这部分是可以读写的 
    
    struct class_rw_t {
        // Be warned that Symbolication knows the layout of this structure.
        uint32_t flags;
        uint16_t version;
        uint16_t witness;
        const class_ro_t *ro;   //不可写部分
        method_array_t methods; //方法列表
        property_array_t properties;// 属性列表
        protocol_array_t protocols; //协议列表
        Class firstSubclass; 
        Class nextSiblingClass;
        char *demangledName;
    }
    
    这是 class_ro_t  可读不可写的部分 
    struct class_ro_t {
        uint32_t flags;
        uint32_t instanceStart;
        uint32_t instanceSize;   //实例的大小
    #ifdef __LP64__
        uint32_t reserved;
    #endif
      t 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 // 指向元类指针
    • superclass //指向父类的指针
    • cache //方法缓存列表,注意是对象方法
    • bits //里边有基本的可变的一堆属性和不可变的一堆属性

    针对bits里边可变不可变的属性做个补充:

    1. method 和 protocol 这些通过运行时可以动态添加;properties可以在分类中添加(只不过值是存储在一个全局的散列表中的)
    2. ivars这个是不可变的,也就是说,不能向一个已经创建好的类中添加ivar,这是因为,在实例化实例对象的时候,内存大小是固定的。(instanceSize)
      实例结构体中包含的就是ISA指针和ivars的列表。

    元类对象
    元类对象是类对象的一种,内部结构和类对象一样,在创建类对象的时候,会同时创建一个元类对象出来。
    只不过内部存储的值和类对象是不一样的,元类对象中存储的是类方法

    • ISA // 指向基类元类指针(注:此处的ISA指针直接指向NSObject的元类)
    • superclass //指向父类的元类的指针
    • cache //方法缓存列表,注意类象方法
    • bits //类方法

    总结:起始根据这三种对象,体现了程序的分层设计的原则,最上层包含的是成员变量、下一层包含各种方法和缓存、再进一层就是只有类才能调用的类方法。

    3.占用空间的大小

    我们创建一个实例对象,占用的内存空间的大小是怎么样的,找到最上边实例的结构体样式。

    • ISA //指向父类的指针
    • _ivar //成员变量
    • _ivar //成员变量
    • _ivar //成员变量
      我们可以看到,包含一个ISA指针,还有不等的ivar,占用的大小可以直接算出,指针变量8个字节,int 类型 4个字节... 相加就得出了。
      但是注意:结构体在创建的时候,有内存对齐的概念,必须是8的整数倍,也就是当内存占用20个字节,会给你分配24个字节。
      C函数还有一层内存对齐的概念,他分配内存大小是16的整数倍
    4.方法调用

    直接拿网上的图


    查找流程.png

    先看ISA 和 superclass这两个指针的指向
    ISA - > 父类
    父类ISA - >元类
    元类ISA - > Root Meta Class

    Subclass 的superclass指针 - > Superclass
    Superclass 的 superclass指针 - > NSObject

    对象方法:根据ISA指针找到自己的父类-- >查找父类cache缓存 -->查找父类methodlist --> 根据superclass指针 -- > Superclass的cache --- > Superclass的methodlist ---> NSObject...

    类方法:根据ISA指针找到Metaclass的cache -- > Metaclass 的methodlist -- > 根据superclass指针周到 Superclass Meta的cache ....

    其他补充

    1.关于ISA指针中存储的地址不是类对象首地址的原因
    由于在新版ISA指针做了优化,8个字节 64位的存储空间,不仅存储指向类对象的地址,还保存了一些标志位信息,包括是否有关联对象、是否有弱引用、引用计数存储等。所以要取出这个地址还是要 & 上下边的地址。


    ISA_MASK2020-02-29上午8.31.58.png

    2.类方法在寻找的时候,找到NSObject的元类中,如果还是没有,还会去NSObject类中寻找是否有相同符号的对象方法。
    3.获取元类的方式object_getClass([NSObject class]);传递一个类对象进去,就可以获取。

    相关文章

      网友评论

          本文标题:OC对象的本质

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