Runtime之NSObject结构

简介

OC是一门面向对象的语言,而对于面向对象语言来说一切皆对象。相信每个iOS开发者都很清楚在OC中NSObject是绝大多数对象的父类。OC是一门动态语言,而动态的实现则是离不开Runtime。那么OC中的对象在Runtime中又是以一种什么样的形态出现的呢?本篇文章我们来详细介绍OC对象在Runtime中的结构。

NSObject

我们先来看下在RuntimeNSObject的结构

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}

从代码中我们可以看到,实际上对于一个NSObject对象 首先遵守了<NSObject>协议同时有一个isa指针,这个指针是Class类型的。

那么Class的结构呢?

typedef struct objc_class *Class;

我们可以简单的理解为:Class是一个指向objc_class类型结构体的指针

那么objc_class的结构又是什么样的呢?

对于这个结构首先,我们在runtime.h中找到了下面这个定义

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;

这个定义应该是我们在平时查看objc_class结构的时候最长见到的结构了,但是我们注意后面实际上有OBJC2_UNAVAILABLE这个标识,表示在objc2中已经不可用了。因此这个结构不具有参考价值,我们继续来查找objc_class结构体。

objc-runtime-new.h文件中我们发现了新的定义:

/// OC 中对象的结构体
struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    // bits用于存储类名、类版本号、方法列表、协议列表等信息,替代了Objective-C1.0中methodLists、protocols等成员变量。
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    // class_rw_t 表示 class 是readwrite的 class_ro_t 表示class是readonly的
    class_rw_t *data() { 
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
  }
  // ....省略一些方法

从上面的结构中我们可以看到:类实际上也是一个对象(类对象),主要包括superclasscachebits三个属性。下面我们分别来看下这三个属性的意义。

superclass 父类

superclass首先也是一个class类型,表示这个类的父类。

superclassobjc_class结构中除了标识这个类对象是哪种类型的之外,还用在了下面几个方法中

isRootClass 根类
bool isRootClass() {
        return superclass == nil;
}

当superclass为nil时则表明当前的类对象没有父类 同时也就意味着这个类是rootClass。

cache 缓存

cache即缓存,那么缓存中存放的内容是什么呢?我们先来看下cache_t的结构

struct cache_t {
    // 是一个指向 bucket_t 结构体的哈希表
    struct bucket_t *_buckets;
    // 是一个 uint32_t 的指针,表示整个 _buckets 哈希表的长度
    mask_t _mask;
    // _occupied 也是一个 uint32_t 的指针,在 _buckets 哈希表中已经缓存的方法数量

public:
    struct bucket_t *buckets();
    mask_t mask();
    mask_t occupied();
    //.....
};

cache_t结构中主要包含了一个bucket_t类型的数组_buckets。我们继续看下bucket_t的结构。

bucket_t 方法缓存
struct bucket_t {
private:
    // IMP-first is better for arm64e ptrauth and no worse for arm64.
    // SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
    MethodCacheIMP _imp;
    cache_key_t _key;
#else
    // unsigned long 的指针,其实是一个被 hash 化的一串数值,就是方法的 sel
    cache_key_t _key;
    // 保存着对应的函数地址
    MethodCacheIMP _imp;
#endif

public:
    inline cache_key_t key() const { return _key; }
    inline IMP imp() const { return (IMP)_imp; }
    inline void setKey(cache_key_t newKey) { _key = newKey; }
    inline void setImp(IMP newImp) { _imp = newImp; }

    void set(cache_key_t newKey, IMP newImp);
};

每个bucket_t实际上有一个key和imp(暂时不考虑为何在arm64和armv7e i386以及x86之间的顺序区别),并包含了设置key,imp以及取值的方法。那么这个缓存在什么时间被填充和使用呢?

实际上在oc中每个类的方法调用都维护了一个cache,当A类的a方法被调用时,A类的cache存放了a方法的实现和key,如果下次再调用了a方法,那么直接从缓存中取出执行。如果缓存中没有找到,那么在去类的方法列表中进行查找,如果找到了,那么执行这个方法并将方法塞到缓存中,方便下此调用。

bits

同样我们首先看下这个属性的类型为class_data_bits_t,我们再看下这个类型的结构

class_data_bits_t
struct class_data_bits_t {

    // Values are the FAST_ flags above.
    uintptr_t bits;
private:
    bool getBit(uintptr_t bit)
    {
        return bits & bit;
    }
    /// ......省略
public:
    class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    void setData(class_rw_t *newData)
    {
        assert(!data()  ||  (newData->flags & (RW_REALIZING | RW_FUTURE)));
        // Set during realization or construction only. No locking needed.
        // Use a store-release fence because there may be concurrent
        // readers of data and data's contents.
        uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
        atomic_thread_fence(memory_order_release);
        bits = newBits;
    }
}

class_data_bits_t结构体中只有一个64位的指针bits,它相当于 class_rw_t 指针加上 rr/alloc 等标志位。其中class_rw_t指针存在于4~47位(从1开始计)

is_swift标记位标示是否为swift的类。

class_data_bits_t结构体中,我们看到有一个data的setter和getter方法。

我们先来看下设置方法的实现:

setData(class_rw_t *newData)
// (bits & ~FAST_DATA_MASK)现将当前存储的值bits与之前定义好的掩码进行按位与操作 相当于取出标志位 3-46位
// 将前一步得到的值与外部传入的值进行按位或操作 设置新的标志位的值
uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
atomic_thread_fence(memory_order_release);
// 将操作后的结果保存着唉bits中
bits = newBits;

带着这个我们在去看getter方法:

class_rw_t *data()
class_rw_t* data() {
    return (class_rw_t *)(bits & FAST_DATA_MASK);
}

先通过与掩码进行按位与操作 获取到所有的标志位 然后返回。

类似的标志位还有:


// class or superclass has .cxx_construct implementation
// 第 18 位的值是否为 1,以此表示该类或者父类是否有 .cxx_construct 函数实现
#define RW_HAS_CXX_CTOR       (1<<18)
// class or superclass has .cxx_destruct implementation
// 第17位的值是否为 1 一次来表示类或者父类有 .cxx_destruct 函数实现。
#define RW_HAS_CXX_DTOR       (1<<17)

// class or superclass has default alloc/allocWithZone: implementation
// Note this is is stored in the metaclass.
// 第 16 位的值是否为 1,以此表示该类或者父类是否有 alloc/allocWithZone 函数的默认实现
#define RW_HAS_DEFAULT_AWZ    (1<<16)
// class's instances requires raw isa
// 第 15 位的值是否为 1,以此表示类实例对象(此处是指类对象,不是使用类构建的实例对象,一定要记得)是否需要原始的 isa。
#if SUPPORT_NONPOINTER_ISA
#define RW_REQUIRES_RAW_ISA   (1<<15)
#endif

// class or superclass has default retain/release/autorelease/retainCount/
//   _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference
// 第 14 位的值是否为 1,以此表示该类或者父类是否有如下函数的默认实现
#define RW_HAS_DEFAULT_RR     (1<<14)

// class is a Swift class from the pre-stable Swift ABI
// class 是来自稳定的 Swift ABI 的 Swift 类。(遗留的类)
#define FAST_IS_SWIFT_LEGACY  (1UL<<0)

// class is a Swift class from the stable Swift ABI
// class 是一个有稳定的 Swift ABI 的 Swift类。
#define FAST_IS_SWIFT_STABLE  (1UL<<1)
// data pointer
// 一个定义好的掩码 二进制第 3-46 位是 1,其他位都是 0
#define FAST_DATA_MASK        0xfffffffcUL

我们可以看到在getter方法中返回的实际上是一个class_rw_t结构类型的指针,那么这个结构体的结构是什么样的呢?

class_rw_t
struct class_rw_t {
    // 只读class 结构体
    const class_ro_t *ro;
    //方法列表
    method_array_t methods;
    //属性列表
    property_array_t properties;
    //协议列表
    protocol_array_t protocols;
}

这个结构体中我们发现了一个class_ro_t的常量,class_ro_tclass_rw_t的最大区别在于一个是只读的,一个是可读写的,实质上ro就是readonly的简写,rwreadwrite的简写。

对于method_array_t methodsproperty_array_t propertiesprotocol_array_t protocols我们可以简单的从命名上看出分别对应 方法列表、属性列表、协议列表。

我们在进一步看下class_ro_t的结构体:

class_ro_t
struct class_ro_t {
    //
    const char * name;
    // 方法列表
    method_list_t * baseMethodList;
    // 协议列表
    protocol_list_t * baseProtocols;
    //成员变量列表
    const ivar_list_t * ivars;
};

结构与class_rw_t类似分别包含了只读的方法列表、协议列表和成员变量列表。

那么class_rw_tclass_ro_t的区别和关系是什么呢?

  • class_ro_t存放的是编译期间就确定的;而class_rw_t是在runtime时才确定
  • class_rw_oclass_rw_t中的一个属性,所以可以说class_rw_tclass_ro_t的超集
  • 在程序运行期间实际上我们访问的方法、属性、协议的列表都是访问class_rw_t中的

那么class_rw_t是如何被创建的,他的class_ro_t属性又是如何被赋值的呢?
当dyld调用load_images将镜像加载到内存后,然后依次会map_images->_read_images->realizeAllClasses->realizeClass
我们来看下objc-runtime-new.mrealizeClass方法中的一段代码:

ro = (const class_ro_t *)cls->data();
//是否已经初始化过,初始化过的哈 则 cls->rw 已经初始化过
if (ro->flags & RO_FUTURE) {
    rw = cls->data();
    ro = cls->data()->ro;
    cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
    // 正常情况下 申请class_rw_t空间
    rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
    rw->ro = ro;//cls->rw->ro 指向现在的ro
    rw->flags = RW_REALIZED|RW_REALIZING;//realized = 1 and  realizing = 1
    cls->setData(rw);//赋值
    }

从上面代码中我们可以看到 cls->data() 最开始实际上是readonly的,我们在类初始化的时候,创建一个rw,然后将ro的值赋值给rw->ro,然后将rw的标志位赋值。最后赋值给cls->rw

这个过程我们可以通过下图更加详细的区分,首先是编译过程中:

在runtime执行了realizeClass之后

总结

这样我们就把NSObject在runtime中相关的结构体都看了一遍,通过上面的了解我们来重新看下NSObject的结构:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值