OC对象的结构

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 ro_or_rw_ext;
    Class firstSubclass;
    Class nextSiblingClass;
 private:
    using ro_or_rw_ext_t = objc::PointerUnion;   
    ...
public:
const class_ro_t *ro() const { //获取class_ro_t类信息
        auto v = get_ro_or_rwe();
        if (slowpath(v.is())) {
            return v.get()->ro;
        }
        return v.get();
    }
const method_array_t methods() const { //方法列表
        auto v = get_ro_or_rwe();
        if (v.is()) {
            return v.get()->methods;
        } else {
            return method_array_t{v.get()->baseMethods()};
        }
    }
const property_array_t properties() const { //属性列表
        auto v = get_ro_or_rwe();
        if (v.is()) {
            return v.get()->properties;
        } else {
            return property_array_t{v.get()->baseProperties};
        }
    }
const protocol_array_t protocols() const { //协议列表
        auto v = get_ro_or_rwe();
        if (v.is()) {
            return v.get()->protocols;
        } else {
            return protocol_array_t{v.get()->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 = 0x00000001003ea460 {
        _sel = {
          std::__1::atomic = 0x0000000000000000
        }
        _imp = {
          std::__1::atomic = 0
        }
      }
    }
    _mask = {
      std::__1::atomic = 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 = 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 _buckets;//explicit_atomic相当于做了一个线程保护,实际类型为一个结构体指针占8个字节
    explicit_atomic _mask;//typedef uint32_t mask_t; 占4个字节
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16//(__arm64__) && __LP64__真机设备
    explicit_atomic _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 _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 = 4294976120
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}
  • 总结,上面两种方法都可以获取到对象的信息剩下的操作就一样的了,不过这种lldb调试需要在搭配好的源码环境下才能正常运行。

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

  • 根据源码的的对象结构,定义下面对应的结构体
struct xq_bucket_t {
//#if __arm64__
//    explicit_atomic _imp;
//    explicit_atomic _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对象的结构)