Objective-C 之 Runtime 数据结构篇

2020/5/30 23:26:34

本文主要是介绍Objective-C 之 Runtime 数据结构篇,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

对象

当不确定一个对象的类型的时候,通常使用 id 类型进行表示

- (id)copy;
复制代码

id 代表一个对象,它是指向一个实例对象的指针

typedef struct objc_object *id;
复制代码

实际上,id 是一个 objc_object 结构体类型指针的别名

struct objc_object {
    isa_t isa;
};
复制代码

objc_object 这个结构体中只有一个 isa_t 类型的成员 isa,它包含了当前对象所属于的类的信息。

isa_t

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};
复制代码

isa_t 是一个联合体,这就意味着 isa_t 中保存的既可以是一个 Class 类型的指针,也可以是一个 64 位的 bits,但在某一个时刻,只能保存二者中的一个。

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
#   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 deallocating      : 1;                                       \
      uintptr_t has_sidetable_rc  : 1;                                       \
      uintptr_t extra_rc          : 19
#   define RC_ONE   (1ULL<<45)
#   define RC_HALF  (1ULL<<18)

# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   define ISA_BITFIELD                                                        \
      uintptr_t nonpointer        : 1;                                         \
      uintptr_t has_assoc         : 1;                                         \
      uintptr_t has_cxx_dtor      : 1;                                         \
      uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
      uintptr_t magic             : 6;                                         \
      uintptr_t weakly_referenced : 1;                                         \
      uintptr_t deallocating      : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8
#   define RC_ONE   (1ULL<<56)
#   define RC_HALF  (1ULL<<7)
复制代码

在不同架构下 bits 内存布局有所不同,但是结构体中的成员和其代表的含义都是相同的,只是具体结构体的实现和位数可能有些差别。

isa 初始化

inline void 
objc_object::initClassIsa(Class cls)
{
    if (DisableNonpointerIsa  ||  cls->instancesRequireRawIsa()) {
        initIsa(cls, false/*not nonpointer*/, false);
    } else {
        initIsa(cls, true/*nonpointer*/, false);
    }
}

inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    initIsa(cls, true, hasCxxDtor);
}

复制代码

不论是初始化类还是实例的 isa 都是调用了下面的方法

inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{   
    if (!nonpointer) {
        isa = isa_t((uintptr_t)cls);
    } else {
        isa_t newisa(0);
        newisa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;
        isa = newisa;
    }
}
复制代码

如果是老版本 isa 中就是保存 Class 类型的指针。如果是新版本,就对 64 位的 bits 进行配置

其中 ISA_MAGIC_VALUE 实际上配置了结构体中成员 nonpointermagic 的值。

  • nonpointer 如果是 0 那么 isa 中就是保存 Class 类型的指针,如果是 1 就保存的是 bits
  • magic 用于调试器判断当前对象是真的对象还是没有初始化的空间

接着配置了 has_cxx_dtor 的值,该值表示当前对象有 C++ 或者 ObjC 的析构器(destructor),如果没有析构器就会快速释放内存。

最后将类的指针右移了 3 位后赋值给了成员 shiftcls。这是因为类的指针是按照字节(8bits)对齐的,其指针后三位都是没有意义的 0,因此可以右移 3 位进行消除,以减小无意义的内存占用。

isa_t 成员

成员名 含义
nonpointer 区分新老版本,老版本是 Class cls,新版本是 uintptr_t bits
has_assoc 对象含有或者曾经含有关联引用,没有关联引用的可以更快地释放内存
has_cxx_dtor 对象有 C++ 或者 ObjC 的析构器(destructor),如果没有析构器就会快速释放内存
shiftcls 类的指针
magic 调试器判断当前对象是真的对象还是没有初始化的空间
weakly_referenced 对象被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放
deallocating 对象正在释放内存
has_sidetable_rc 对象的引用计数太大了,存不下
extra_rc 对象的引用计数超过 1,会存在这个这个里面,如果引用计数为 10,该值就为 9

在获取一个对象所属的类时,返回值的类型是 Class

- (Class)class;
复制代码

Class 代表一个类,它是指向一个类对象的指针

typedef struct objc_class *Class;
复制代码

实际上,Class 是一个 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_class 是继承自 objc_object,也就是说类也是一个对象。其中从 objc_object 继承下来的成员 isa,它包含了当前类对象所属的元类的信息。

  • 其次,成员 superclass 指向了该类的父类

  • 接着,成员 cache 中缓存了最近调用过的方法

  • 最后,成员 bits 根据注释可以知道 bits = 指向 class_rw_t 类型结构体的指针 + 自定义的 rr/alloc 标识

cache_t

struct cache_t {
    explicit_atomic<struct bucket_t *> _buckets;
    explicit_atomic<mask_t> _mask;
    uint16_t _occupied;
};
复制代码

用来缓存调用过方法的是一个 cache_t 类型的结构体,共有三个成员。

第一个成员 _buckets 是一个 bucket_t 类型的数组。

第二个成员 _mask 保存着总共申请用来缓存的数量。

第三个成员 _occupied 保存着目前已经使用缓存的数量。

bucket_t

struct bucket_t {
#if __arm64__
    explicit_atomic<uintptr_t> _imp;
    explicit_atomic<SEL> _sel;
#else
    explicit_atomic<SEL> _sel;
    explicit_atomic<uintptr_t> _imp;
#endif
};
复制代码

bucket_t 结构体中存储了键值对,其中 _imp 是一个函数指针,指向函数的具体实现,_sel 是方法名或者方法标识。也就说 _buckets 是一个哈希表,里面通过键值对的方式保存着调用过的方法。键为 SEL 类型,值为 IMP 类型。

SEL

typedef struct objc_selector *SEL;
复制代码

从声明上看 SEL 是一个 objc_selector 结构体指针的别名;从实现中找,发现 SEL 是指向一个 char * 变量,也就是说其实它就是个映射到方法的 C 字符串,可以理解为是区分方法的 ID。

SEL 只与方法名有关:

  • 不同类中相同名字的方法所对应的方法选择器是相同的
  • 方法名相同而变量类型不同所对应的方法选择器也是相同的

IMP

#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
#endif
复制代码

IMP 是一个函数指针,它指向函数的具体实现。

class_data_bits_t

bits 中保存着类的相关信息,它是一个 class_data_bits_t 类型的结构体,

struct class_data_bits_t {
    // Values are the FAST_ flags above.
    uintptr_t bits;
};
复制代码

这个结构体中的成员和 isa_t 结构体相同,都是通过一个 64 位的 bits 储存信息

// class is a Swift class from the pre-stable Swift ABI
#define FAST_IS_SWIFT_LEGACY    (1UL<<0)
// class is a Swift class from the stable Swift ABI
#define FAST_IS_SWIFT_STABLE    (1UL<<1)
// class or superclass has default retain/release/autorelease/retainCount/
//   _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference
#define FAST_HAS_DEFAULT_RR     (1UL<<2)
// data pointer
#define FAST_DATA_MASK          0x00007ffffffffff8UL
复制代码

通过定义的宏可以知道这些位都代表了什么含义

  • FAST_IS_SWIFT_LEGACY 是否是来自预稳定 Swift ABI 的 Swift 类
  • FAST_IS_SWIFT_STABLE 是否是来自已稳定 Swift ABI 的 Swift 类
  • FAST_HAS_DEFAULT_RR 当前类或者父类含有默认的 retain/release/autorelease/retainCount/_tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference 方法
  • FAST_DATA_MASK 数据指针

搜索 FAST_DATA_MASK 的使用位置,可以找到一对取值/赋值方法

class_rw_t* data() const {
    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_rw_t 类型的指针

class_rw_t

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_rw_t 名称中的 rw 代表 readwrite,其中的成员 ro 是一个 class_ro_t 类型的指针,这里的 ro 代表 readonlyro 中存储了当前类在编译期就已经确定的属性、方法以及遵循的协议。而 class_rw_t 提供了运行时对类拓展的能力,其中的 methodspropertiesprotocols 保存着通过 Category 在运行时添加的方法、属性及协议。

成员 methodspropertiesprotocols 对应的 method_array_tproperty_array_tprotocol_array_t 类型都是继承自 list_array_tt<Element, List>,该类型可以保存三种类型的值:

  1. 空值;
  2. 指向单个列表的指针;
  3. 指向列表的指针数组。

通过第 3 种类型也就是二维数组类型,可以实现数据的扩展。例如,method_array_t 是一个数组,其中保存的元素是 method_list_t,而 method_list_t 也是一个数组,其中保存的元素是 method_t

class_ro_t

相对的,class_ro_t 保存的就是在编译期确定的数据。

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    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;
};
复制代码

结构体中的成员 baseMethodListbasePropertiesbaseProtocolsivar_list_t 对应的 method_list_tproperty_list_tprotocol_list_tivar_list_t 类型都是继承自 entsize_list_tt,一个泛型的数组结构,没有扩展功能。

类的初始化

类在运行时第一次初始化时会调用 realizeClass... 系列方法

ro = (const class_ro_t *)cls->data();
if (ro->flags & RO_FUTURE) {
    // This was a future class. rw data is already allocated.
    rw = cls->data();
    ro = cls->data()->ro;
    cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
    // Normal class. Allocate writeable class data.
    rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
    rw->ro = ro;
    rw->flags = RW_REALIZED|RW_REALIZING;
    cls->setData(rw);
}
复制代码

在该方法的实现中,可以知道在编译期间类的结构中的 class_data_bits_t *data 指向的是一个 class_ro_t *。在运行时的时候调用该初始化方法时,首先为 class_rw_t * 申请内存空间,然后将 class_ro_t * 赋值给 rw->ro,接着设置 class_rw_t * 的标志位标识状态,最后将创建好的 class_rw_t * 赋值给类结构。

分类

Category 提供了在运行时动态的向已经存在的类中添加方法、协议和属性的功能

typedef struct category_t *Category;
复制代码
struct category_t {
    const char *name;
    classref_t cls;
    struct method_list_t *instanceMethods;
    struct method_list_t *classMethods;
    struct protocol_list_t *protocols;
    struct property_list_t *instanceProperties;
    // Fields below this point are not always present on disk.
    struct property_list_t *_classProperties;
};
复制代码

加载分类

static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                 int flags)
{
    constexpr uint32_t ATTACH_BUFSIZ = 64;
    method_list_t   *mlists[ATTACH_BUFSIZ];

    uint32_t mcount = 0;
    bool fromBundle = NO;
    bool isMeta = (flags & ATTACH_METACLASS);
    auto rw = cls->data();

    for (uint32_t i = 0; i < cats_count; i++) {
        auto& entry = cats_list[i];

        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            if (mcount == ATTACH_BUFSIZ) {
                prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
                rw->methods.attachLists(mlists, mcount);
                mcount = 0;
            }
            mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
            fromBundle |= entry.hi->isBundle();
        }
    }

    if (mcount > 0) {
        prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle);
        rw->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
        if (flags & ATTACH_EXISTING) flushCaches(cls);
    }
}
复制代码

原来的方法中包含了对分类中方法、代理和属性的处理,但原理相同,为了减少篇幅,只保留对方法的处理:

  • 声明一个大小为 64,元素类型为 method_list_t * 的二维数组类型的临时变量 mlists
  • 遍历分类数组,获取分类中的方法列表 mlist,从后向前的添加到 mlists
  • 如果 mlists 数组在遍历过程中存满,则合并到 rw->methods 中,否则等待遍历完合并

合并方法

void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;

        if (hasArray()) {
            // many lists -> many lists
            uint32_t oldCount = array()->count;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
            array()->count = newCount;
            memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            list = addedLists[0];
        } 
        else {
            // 1 list -> many lists
            List* oldList = list;
            uint32_t oldCount = oldList ? 1 : 0;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)malloc(array_t::byteSize(newCount)));
            array()->count = newCount;
            if (oldList) array()->lists[addedCount] = oldList;
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
    }
复制代码

在合并时,大致有两种情况。一种是当前数组中为空,那就直接指向新添加的数组;另一种是当前数组中已经有数据:

  1. 先通过 realloc() 方法为原数组扩充空间
  2. 再将原数组的元素利用 memmove() 方法移动到新申请空间的后面
  3. 最后使用 memcpy() 方法把新数组复制到新空间的前面

方法覆盖

通过上面两段代码,可以发现两点:

  1. 分类中如果重写了方法,并不会覆盖原有方法
  2. 之所以在使用时出现“覆盖”原方法的现象,是因为在方法列表中分类中的方法被添加到了原方法的前面
  3. 如果某个类的多个分类都重写了同一个方法,那么,最后执行的是最后被遍历到的分类(也就是最后被添加到项目中的分类)中的方法


这篇关于Objective-C 之 Runtime 数据结构篇的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程