OC归纳总结 -- (1)OC对象的原理与类原理

306 阅读3分钟

OC对象的原理

OC对象通过clang++编译成C++以后能看到,每个OC对象是一个struct对象,并且在内部有一个isa指针。

在OC中,实际拥有三种对象

  1. Instance实例对象
  2. Class类对象
  3. Meta-Class元类对象

他们三者在内存中的结构是一样的都是:

struct NSObject_IMPL{
    Class isa;
};
typedef struct objc_class *Class;

简单来说这三种对象第一个成员变量都是isa指针!!!

对于上面三种常见的三种对象来说他们的架构分别如下:

1. Instance对象 - 实例对象
- isa指针
- 其他成员变量例如 _age, _name等
​
2. Class对象 - 类对象
- isa 指针 - 可以通过 mask获取
- superclass 指针
- class_data_bits_t bits -通过FAST_DATA_MASK获取具体指针
​
3. Meta-Class - 元类对象 - 结构同 Class对象

注意在arm64下apple对isa指针占用64bit进行了优化, 使用ISA_MASK对isa指针&操作才能获取到真正的isa地址!!! isa的其他bit有另做它用,后面再总结.

上面内容比较关键的是Class对象和MetaClass对象, 他们的内存结构一致. 核心是isa,superclass,和bits.

其中bits用于获取具体的类信息!!!

bits-> 通过&FAST_DATA_MASK -> class_rw_t指针 -> class_ro_t指针

最为关键的就是class_rw_t结构体:

// NSObject 的结构体, 实际内部只有一个isa成员变量
struct objc_object {
private:
    isa_t isa;
}
​
// OC 中所有的类实际也是一个 objc_object, 万物皆对象
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
    ...
}
​
// bits&FAST_DATA_MASK 获取的rw指针
// 可读可写信息 -- runtime动态添加方法 + category分类添加方法都在这里
struct class_rw_t {
    const class_ro_t *ro; // 类对象的原始信息
    method_list_t *methods; // 类的方法 + 分类方法
    .. properties; // 属性 @property...
    .. protocols;  // 协议方法
}
​
// 只读信息 - 编译时类的方法
struct class_ro_t {
    ...
    ... baseMethodLists; // 方法
    ... peroperties;     // 属性
    ... protocols;       // 协议方法
    ... ivars; // 成员变量列表
}

对于类结构中还有一个经典图(这里就不贴了网上很多), 通过该图可以非常容易的理解oc对象方法查找的搜索流程:

  • Instance 调用对象方法的轨迹:isa 找到 Class,方法不存在,就通过superclass 找父类。
  • Class 调用类方法的轨迹:isa 找 Meta-Class,方法不存在,就通过 superclass 找父类

简单来说, isa指向的内容如下:

instance对象(拥有isa) -> class对象(类)-> meta-class对象(元类) -> NSObject(根元类) -> NSObject(根元类)

isa流程图.png

arm64中的isa指针以及指针优化

在Arm64中apple对isa指针使用位域方式进行优化,isa指针一共64个bit,关键位置上的意义如下:

# if __arm64__
#     define ISA_MASK        0x0000000ffffffff8ULL
#     define ISA_MAGIC_MASK  0x000003f000000001ULL
#     define ISA_MAGIC_VALUE 0x000001a000000001ULL
#     define ISA_HAS_CXX_DTOR_BIT 1
#     define ISA_BITFIELD 
        uintptr_t nonpointer        : 1;                                       \
        uintptr_t has_assoc         : 1;                                       \
        uintptr_t has_cxx_dtor      : 1;                                       \
        uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
        uintptr_t magic             : 6;                                       \
        uintptr_t weakly_referenced : 1;                                       \
        uintptr_t unused            : 1;                                       \
        uintptr_t has_sidetable_rc  : 1;                                       \
        uintptr_t extra_rc          : 19
#     define RC_ONE   (1ULL<<45)
#     define RC_HALF  (1ULL<<18)
#   endif

其中关键bit的解释:

1. nonpointer -- 表示是否开启isa指针优化!!! 如果是 0 表示没有开启,此时isa就是一个指针
// 如果nonpointer是1, 后面的说明才有意义!!!
2. has_assoc -- 标记当前对象是否有关联对象
3. has_cxx_dtor
4. shiftcls  -- 真正的 isa指针存储的地址信息!!!
5. magic
6. weakly_referenced -- 标记该对象是否曾经拥有弱引用对象
7. deallocating -- 对象是否正在释放
8. has_sidetable_rc -- 标记是否拥有引用计数表帮助进行引用计数管理
9. extra_rc -- 19bit 用来记录当前对象的引用计数信息(如果位数不够,会使用引用计数表)

其中与后续一些关键的知识点有关系的是:

  1. has_assoc 关联对象 association object
  2. weakly_referenced 弱引用
  3. has_sidetable_rc 引用计数表

PS:补充内容

另外与arm64有关的是 Tagged Pointer,存储一些小对象时候,Tagged Pointer指针的不再指向堆中地址,⽽是真正的值, Apple直接通过将指针&TAG_MASK去判断是否该指针是Tagged Pointer指针!!!

static inline bool 
_objc_isTaggedPointer(const void * _Nullable ptr) {
    return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}