runtime剖析的真是越来越复杂了。。。

isa_t类型详解

在新版的runtime源码中,NSObject类型最终会转化为object_class类型,而object_class集成自objc_object,在结构体objc_object中就含有isa_t类型的成员isa

查看isa_t的源码,其中处理两个构造函数外,有一个cls指针,还有一个uintptr_t类型的成员bits以及一个结构体:

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
}

查看结构体的源码可以发现,在结构体中使用位域来存储了很多信息,此处展示arm64架构下的源码信息

#define ISA_MASK        0x0000000ffffffff8ULL
#define ISA_MAGIC_MASK  0x000003f000000001ULL
#define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
    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
};

isa_t位域存放信息类型

isa_t作为公用体,内部使用8个字节的内存空间,共64位二进制,存放了以下信息
 * nonpointer代表是否是优化过的isa指针,占用1位。
  * 1:表示新版本的isa指针,使用位域来存储信息
  * 0:旧版本普通的isa指针,直接存储Class和Meta-Class内存地址
  * has_assoc代表是否有关联对象,占用1位,一旦设置过关联对象,则会置为1。如果添加过关联对象,在释放时会检测是否有关联对象,所以释放会更慢。
  * has_cxx_dtor代表是否实现了C++的析构函数(.cxx_destruct),如果没有,释放时的速度会更快。占用1位
  * shiftcls中存放着类或者元类的内存地址,占用33位。
  * magic是调试时用来判断对象是否完成初始化,占用6位
  * weakly_referenced代表是否被弱引用指向过,占用1位,如果为0,则释放时速度会更快
  * deallocating用来表示对象是否正在释放,占用1位
  * extra_rc用来存储引用计数的值,占用19位,此处需要注意的时,它存储的是引用计数的值-1。如果对象的引用计数为1,则extra_rc中存储的值为0
  * has_sidetable_rc用来表示是否将引用计数存储在SideTable中,引用计数的值过大,在extra_rc无法存储,则会将引用计数存放到SideTable当中。

Class底层结构分析

* 上面我们已经分析来isa_t的结构

struct objc_object {
private:
    isa_t isa;
public:
    ......
}

* objc_class继承自结构体objc_object,结构可以简化如下

struct objc_class{
    Class ISA;                       //isa指针,通过位域存放多个信息
    Class superclass;           //supperClass
    cache_t cache;              // 方法缓存
    class_data_bits_t bits;    // 用来获取类的具体信息
}
ojbc_class 除了有isa指针外,还保存了父类的class,方法缓存以及当前类的一些基本信息。

查找class_data_bits_t

  • 其实一开始是没有class_rw_t的,而存放的是class_ro_t,class_rw_t是在之后进行创建的。
  • class_rw_t是可读可写的,它包含了类的初始内容,分类的内容。
  • class_ro_t是只读的,它包含了类初始化的内容,并且在编译完成后就决定了,在运行时无法进行修改。

源码分析

上文提到,在类初始化的时候其实class中保存的是class_ro_t而不是class_rw_t,这一点可以通过objc-runtime-new.mm中的realizeClassWithoutSwift函数可以看出

static Class realizeClassWithoutSwift(Class cls){
    const class_ro_t *ro;
    class_rw_t *rw;
    Class supercls;
    Class metacls;
    bool isMeta;
    
    if (!cls) return nil;
    //如果class已经初始化,则直接返回当前class
    if (cls->isRealized()) return cls;
    assert(cls == remapClass(cls));
    //首先通过class的data()函数取到class中bits中存放的class_ro_t
    ro = (const class_ro_t *)cls->data();
    if (ro->flags & RO_FUTURE) {
        //如果当前的cls是future class,并且rw已经被创建,则直接拿到rw和rw中的ro
        rw = cls->data();
        ro = cls->data()->ro;
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        //如果是普通的class,创建rw
        rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
        //将ro赋值给rw中的ro
        rw->ro = ro;
        //设置rw的flags
        rw->flags = RW_REALIZED|RW_REALIZING;
        //将rw设置到cls中的bits中去
        cls->setData(rw);
    }
    
    ......
    
    //递归初始化父类
    supercls = realizeClassWithoutSwift(remapClass(cls->superclass));
    //递归初始化元类,通过isa指针来获取到cls的元类
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));
    
    ......
    
    //修改rw中的方法列表,属性列表和协议列表,并且将分类中的方法列表,属性列表和协议列表附加到rw中去
    methodizeClass(cls);
}


在类初始化时,cls通过data()函数获取到的其实是class_to_t,内部存放了类初始的方法列表、属性列表和协议列表。如果当前cls是普通的class,则通过calloc函数创建rw,然后将rw中的ro指针指向原始的ro(class_ro_t),之后重置rw中的flags,并将rw的内存地址保存到cls的bits中去。并且,函数中首先是通过递归初始化当前父类以及元类。最后才初始化当前类。

创建完rw(class_rw_t)后,则会重新整理cls中的方法列表、属性列表和协议列表。具体查看methodizeClass函数源码:

static void methodizeClass(Class cls){
    bool isMeta = cls->isMetaClass();
    auto rw = cls->data();
    auto ro = rw->ro;
    // 从ro中拿到baseMethodList
    method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
        //将baseMethodList附加到rw的methods中去
        rw->methods.attachLists(&list, 1);
    }
    // 从ro中拿到baseProperties
    property_list_t *proplist = ro->baseProperties;
    if (proplist) {
        //将baseProperties附加到rw的properties中去
        rw->properties.attachLists(&proplist, 1);
    }
    //从ro中拿到baseProtocols
    protocol_list_t *protolist = ro->baseProtocols;
    if (protolist) {
        //将baseProtocols附加到rw的protocols中去
        rw->protocols.attachLists(&protolist, 1);
    }

    //最后将所有Category的方法列表、属性列表和协议列表附加到cls
    category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
    attachCategories(cls, cats, false /*don't flush caches*/);
    
}

methodizeClass 函数中首先会拿到ro中的方法列表、属性列表和协议列表,然后将拿到的方法、属性列表和协议列表通过对应的attachLists函数附加到rw的二维数组中去。

void attachLists(List* const * addedLists, uint32_t addedCount) {
    if (addedCount == 0) return;
    //这里以方法列表为例
    //array()->lists表示原来类中的方法列表
    //addedLists表示所有Category中的方法列表
    if (hasArray()) {
        //获取原来类中方法列表的长度
        uint32_t oldCount = array()->count;
        //得到方法合并之后的新的数组长度
        uint32_t newCount = oldCount + addedCount;
        //给array重新分配长度为newCount的内存空间
        setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
        array()->count = newCount;
        //将原来array()->lists中的数据移动到数组中oldCount的位置
        //也就是相当于将array()->lists的数据在内存中往后移动了addedCount个位置
        memmove(array()->lists + addedCount, array()->lists,
                oldCount * sizeof(array()->lists[0]));
        //将Category中的方法列表copy到array()->lists中
        //并且是从数组的起始地址开始存放
        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]));
    }

安装完类本身的方法、属性和协议后,会继续通过attachCategories函数拿到class的所有Category中的方法、属性和协议列表,然后调用attachLists函数附加到rw中的二维数组中去。

//将方法列表、属性列表、协议列表附加到类中去
//假设cats中的所有的类别都是按顺序进行加载和排序的,最早装载进内存的类别是第一个
static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);
    //用来判断是否是元类
    bool isMeta = cls->isMetaClass();

    //申请连续内存空间,创建一个二维数组,里面存放着所有的method_list_t
    method_list_t **mlists = (method_list_t **)
        malloc(cats->count * sizeof(*mlists));
    //申请连续内存空间,创建一个二维数组,里面存放着所有的property_list_t
    property_list_t **proplists = (property_list_t **)
        malloc(cats->count * sizeof(*proplists));
    //申请连续内存空间,创建一个二维数组,里面存放着所有的protocol_list_t
    protocol_list_t **protolists = (protocol_list_t **)
        malloc(cats->count * sizeof(*protolists));

    // Count backwards through cats to get newest categories first
    int mcount = 0;
    int propcount = 0;
    int protocount = 0;
    int i = cats->count;
    bool fromBundle = NO;
    //获取到category_list之后,通过逆序遍历来取出Category内部的方法、属性和协议列表
    while (i--) {
        auto& entry = cats->list[i];
        //遍历cls所有的category_t,将category_t中的method_list_t取出,存放到二维数组mlists中
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;
            fromBundle |= entry.hi->isBundle();
        }
        // 将category_t中的property_list_t取出,存放到二维数组proplists中
        property_list_t *proplist = 
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            proplists[propcount++] = proplist;
        }
        //将category_t中的protocol_list_t取出,存放到二维数组protolists中
        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist;
        }
    }
    //拿到类对象cls的class_rw_t类型的成员data,它是可读可写的
    auto rw = cls->data();

    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    //将方法列表合并到rw的方法列表中去,并且插入到表头位置
    rw->methods.attachLists(mlists, mcount);
    free(mlists);
    if (flush_caches  &&  mcount > 0) flushCaches(cls);
    //将属性列表合并到rw的属性列表中去,并且插入到表头位置
    rw->properties.attachLists(proplists, propcount);
    free(proplists);
    //将协议列表合并到rw的协议列表中去,并且插入到表头位置
    rw->protocols.attachLists(protolists, protocount);
    free(protolists);
}

因为是先附加类本身的方法、属性和协议,之后附加Category的方法、属性和协议,并且attachLists操作从数组的头部开始进行附加,所以先执行附加操作的方法、属性和协议会放到数组的后面,因此类本身实现的方法、属性和协议肯定放在rw二维数组的最后一个元素。

说一下对isa指针的理解

isa等价于 is kind of

  • 实例对象isa指向类对象
  • 类对象isa指向元类对象
  • 元类对象isa指向元类的基类

isa有两种类型
* 纯指针,指向内存地址 Class 和 metaClass的内存地址
* NON_POINTER_ISA,除了内存地址外,还存放了一些其他的信息

你可能感兴趣的:(runtime剖析的真是越来越复杂了。。。)