OC对象的结构

作者: 浪的出名 | 来源:发表于2020-09-14 11:50 被阅读0次

    Objective-C的本质

    • 我们平时编写的Objective-C代码,底层实现其实都是C\C++代码


      image
    • 所以Objective-C的面向对象都是基于C\C++的数据结构实现的
    • 将Objective-C代码转换为C\C++代码
    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件
    如果需要链接其他框架,使用-framework参数。比如-framework UIKit
    

    Objective-C对象在内存中的布局

    • 新建一个Person对象,通过观察转换的cpp文件,找到以下结构体定义
    struct Person_IMPL {
        struct NSObject_IMPL NSObject_IVARS;
        int _age;
        int _height;
    };
    
    • 在Person_IMPL中包含一个结构体成员NSObject_IVARS,它是NSObject_IMPL类型.
    struct NSObject_IMPL {
        Class isa;
    };
    
    • 由此可以看出,OC中的对象其实就是通过结构体来实现的。在NSObject_IMPL包含了一个Class类型的成员isa。继续查看Class的定义:
    /// An opaque type that represents an Objective-C class.
    typedef struct objc_class *Class;
    
    • 可以发现其实Class就是一个objc_class类型的结构体指针。在最新的objc4的源码中的objc-runtime-new.h文件中,可以找到最新的objc_class的定义如下:
    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
        ......
    }
    
    • objc_class继承自结构体objc_object,而结构体objc_object的具体定义如下,内部只有一个isa指针
    /// Represents an instance of a class.
    struct objc_object {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    };
    
    • 由于继承关系,结构体objc_class自然也就继承了objc_object的isa指针,所以objc_class也可以转换成如下写法:
    struct objc_class {
        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是继承自objc_object的属性
    • superclass表示当前类的父类
    • cache则代表方法缓存。
    • bits是class_data_bits_t类型的属性,用来存放类的具体信息。
    • 查看class_data_bits_t的具体实现如下:
    //此处只列出核心的代码
    struct class_data_bits_t {
        ......
        class_rw_t* data() {
            return (class_rw_t *)(bits & FAST_DATA_MASK);
        }
        ......
    }
    
    • 这时候发现了通过bits的内部函数data()可以拿到class_rw_t类型的数据,查看class_rw_t的源码如下:
    struct class_rw_t {
        // Be warned that Symbolication knows the layout of this structure.
        uint32_t flags;
        uint16_t witness;
        explicit_atomic<uintptr_t> ro_or_rw_ext;
        Class firstSubclass;
        Class nextSiblingClass;
     private:
        using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t *, class_rw_ext_t *>;   
        ...
    public:
    const class_ro_t *ro() const { //获取class_ro_t类信息
            auto v = get_ro_or_rwe();
            if (slowpath(v.is<class_rw_ext_t *>())) {
                return v.get<class_rw_ext_t *>()->ro;
            }
            return v.get<const class_ro_t *>();
        }
    const method_array_t methods() const { //方法列表
            auto v = get_ro_or_rwe();
            if (v.is<class_rw_ext_t *>()) {
                return v.get<class_rw_ext_t *>()->methods;
            } else {
                return method_array_t{v.get<const class_ro_t *>()->baseMethods()};
            }
        }
    const property_array_t properties() const { //属性列表
            auto v = get_ro_or_rwe();
            if (v.is<class_rw_ext_t *>()) {
                return v.get<class_rw_ext_t *>()->properties;
            } else {
                return property_array_t{v.get<const class_ro_t *>()->baseProperties};
            }
        }
    const protocol_array_t protocols() const { //协议列表
            auto v = get_ro_or_rwe();
            if (v.is<class_rw_ext_t *>()) {
                return v.get<class_rw_ext_t *>()->protocols;
            } else {
                return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols};
            }
        }    
        ...
    
    • class_rw_ext_t的结构:
        const class_ro_t *ro;
        method_array_t methods;
        property_array_t properties;
        protocol_array_t protocols;
        char *demangledName;
        uint32_t version;
    };
    
    • 方法列表methods
    • 属性列表properties
    • 协议列表protocols。
    • 一个class_ro_t类型的只读变量ro

    继续查看class_ro_t的结构如下:

    struct class_ro_t {
        uint32_t flags;
        uint32_t instanceStart;
        uint32_t instanceSize;  //当前instance对象占用内存的大小
        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;//基本属性列表
    }
    

    此处就不得不说class_rw_t和class_ro_t的区别了,class_ro_t中存放着类最原始的方法列表,属性列表等等,这些在编译期就已经生成了,而且它是只读的,在运行期无法修改。而class_rw_t不仅包含了编译器生成的方法列表、属性列表,还包含了运行时动态生成的方法和属性。它是可读可写的。

    通过lldb打印出oc对象的结构

    通过类信息直接打印

    • 可以通过lldb调试打印出objc_class结构体信息
     p (objc_class *)$0
    (objc_class *) $5 = 0x00000001000025b0
    (lldb) p *$5
    (objc_class) $6 = {
      objc_object = {
        isa = {
          cls = 0x0000000100002588
          bits = 4294976904
           = {
            nonpointer = 0
            has_assoc = 0
            has_cxx_dtor = 0
            shiftcls = 536872113
            magic = 0
            weakly_referenced = 0
            deallocating = 0
            has_sidetable_rc = 0
            extra_rc = 0
          }
        }
      }
      superclass = NSObject
      cache = {
        _buckets = {
          std::__1::atomic<bucket_t *> = 0x00000001003ea460 {
            _sel = {
              std::__1::atomic<objc_selector *> = 0x0000000000000000
            }
            _imp = {
              std::__1::atomic<unsigned long> = 0
            }
          }
        }
        _mask = {
          std::__1::atomic<unsigned int> = 0
        }
        _flags = 32804
        _occupied = 0
      }
      bits = (bits = 4320180788)
    }
    (lldb) p $6.bits
    (class_data_bits_t) $7 = (bits = 4320180788)
    (lldb) p $6.data()
    (class_rw_t *) $8 = 0x000000010180ba30
    (lldb) p *$8
    (class_rw_t) $9 = {
      flags = 2148007936
      witness = 0
      ro_or_rw_ext = {
        std::__1::atomic<unsigned long> = 4294976120
      }
      firstSubclass = nil
      nextSiblingClass = NSUUID
    }
    
    • 通过地址偏移获取class_data_bits_t的信息,首先计算class_data_bits_t的偏移值为32(isa-8+superclass-8+cache-16)
    • isa和superclass占8个字节很好理解,我们来分下下cache为何会有16个字节?cache_t的结构如下去掉一些不占字节的(static修饰的和方法)
    struct cache_t {
    #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED//电脑或模拟器
        explicit_atomic<struct bucket_t *> _buckets;//explicit_atomic相当于做了一个线程保护,实际类型为一个结构体指针占8个字节
        explicit_atomic<mask_t> _mask;//typedef uint32_t mask_t; 占4个字节
    #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16//(__arm64__) && __LP64__真机设备
        explicit_atomic<uintptr_t> _maskAndBuckets;// typedef unsigned long           uintptr_t; 8个字节
        mask_t _mask_unused;//和上面一样4个字节
    #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4//(__arm64__) && !__LP64__低于64位的真机设备
        // _maskAndBuckets stores the mask shift in the low 4 bits, and
        // the buckets pointer in the remainder of the value. The mask
        // shift is the value where (0xffff >> shift) produces the correct
        // mask. This is equal to 16 - log2(cache_size).
        explicit_atomic<uintptr_t> _maskAndBuckets;
        mask_t _mask_unused;
    #else
    #error Unknown cache mask storage type.
    #endif
        
    #if __LP64__
        uint16_t _flags;   //typedef unsigned short uint16_t; 2个字节
    #endif
        uint16_t _occupied; //2个字节
    };
    //所以无论在什么设备上cache_t都是16个字节
    
    • 回到刚刚的lldb调试
    (lldb) p/x Person.class
    (Class) $0 = 0x00000001000025b0 Person
    (lldb) p/x (class_data_bits_t *)(0x00000001000025b0 + 0x20)
    (class_data_bits_t *) $1 = 0x00000001000025d0
    (lldb) p *$1
    (class_data_bits_t) $2 = (bits = 4320180788)
    (lldb) p/x $2.data()
    (class_rw_t *) $3 = 0x000000010180ba30
    (lldb) p *$3
    (class_rw_t) $4 = {
      flags = 2148007936
      witness = 0
      ro_or_rw_ext = {
        std::__1::atomic<unsigned long> = 4294976120
      }
      firstSubclass = nil
      nextSiblingClass = NSUUID
    }
    
    • 总结,上面两种方法都可以获取到对象的信息剩下的操作就一样的了,不过这种lldb调试需要在搭配好的源码环境下才能正常运行。

    通过定义结构体的方法查看对象的结构

    • 根据源码的的对象结构,定义下面对应的结构体
    struct xq_bucket_t {
    //#if __arm64__
    //    explicit_atomic<uintptr_t> _imp;
    //    explicit_atomic<SEL> _sel;
    //#else
        IMP _imp;
        SEL _sel;
    //#endif
    };
    struct xq_cache_t {
    //#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    //    struct xq_bucket_t * _buckets;
    //    mask_t _mask;
    //#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
        uintptr_t _maskAndBuckets;
        mask_t _mask_unused;
    //#endif
    //#if __LP64__
        uint16_t _flags;
    //#endif
        uint16_t _occupied;
    
    };
    struct xq_class_data_bits_t {
        // Values are the FAST_ flags above.
        uintptr_t bits;
    
    };
    struct xq_objc_class {
        Class ISA;
        Class superclass;
        struct xq_cache_t cache;             // formerly cache pointer and vtable
        struct xq_class_data_bits_t bits;
    };
    
    Person *p = [Person alloc];
    struct xq_objc_class *pClass = (__bridge struct xq_objc_class *)([Person class]);
    // 通过强转也可以达到调试效果
    

    相关文章

      网友评论

        本文标题:OC对象的结构

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