iOS底层-类的加载之realizeClassWithoutSwift分析

前言

接上篇 类的加载之read_images分析,在上篇探索了map_images_read_imagesreadClassaddNamedClassaddClassTableEntry的流程。

其中,当加载非懒加载类realize non-lazy classes时,首先将类插入到表addClassTableEntry,其次调用realizeClassWithoutSwift用于初始化类的操作。

realizeClassWithoutSwift

主要流程有:rw和ro赋值(占位)父类和元类加载及关联设置成员变量设置标记位methodizeClass赋值(rw和ro真正赋值)

rw 和 ro 赋值

// 从类中获取ro
auto ro = (const class_ro_t *)cls->data();
auto isMeta = ro->flags & RO_META;
// 判断rw是否已经分配 (future class)
if (ro->flags & RO_FUTURE) {
    // 从 future class 获取ro
    rw = cls->data();
    ro = cls->data()->ro();
    ASSERT(!isMeta);
    cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
    // 如果是正常的类
    rw = objc::zalloc<class_rw_t>(); // 开辟内存
    rw->set_ro(ro); // 设置ro
    rw->flags = RW_REALIZED|RW_REALIZING|isMeta; // 设置标记
    cls->setData(rw); // 设置类的rw
}
复制代码

说明:

  • 通过cls->data()获取ro数据。
  • rw开辟空间,将ro赋值给rwro()中。(苹果对此做了对应的优化。具体可以参考WWDC-关于 runtime 的优化)
  • 设置rwflags,其中RW_REALIZED(31位)表示rw是否已经初始化完毕;RW_REALIZING(19位)表示rw是否初始化中;isMeta(0位)是否是元类。
  • 赋值rwcls

但是,经过实际调试,在执行完该流程后,baseMethodList为空。

image.png

说明此处并不是真正意义上的赋值,而是根据类的offset情况去占用对应的位置。

父类和元类加载及关联

父类元类加载

// 递归加载父类、元类
supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
复制代码

递归实例化父类元类

ISA纯指针处理

#if SUPPORT_NONPOINTER_ISA
    if (isMeta) {
        // 如果是元类,其ISA是纯指针的情况
        // 设置原始的ISA到缓存
        cls->setInstancesRequireRawIsa();
    } else {
        bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
        bool rawIsaIsInherited = false;
        static bool hackedDispatch = false;
        // 对应环境变量中的 OBJC_DISABLE_NONPOINTER_ISA配置
        if (DisableNonpointerIsa) {
            // 如果环境变量true后,isa是一个纯指针。
            instancesRequireRawIsa = true;
        }
        // 如果是os_object类时纯指针
        else if (!hackedDispatch  &&  0 == strcmp(ro->getName(), "OS_object"))
        {
            hackedDispatch = true;
            instancesRequireRawIsa = true;
        }
        // 如果父类是纯指针,并且父类的父类存在。
        else if (supercls  &&  supercls->getSuperclass()  &&
                 supercls->instancesRequireRawIsa())
        {
            instancesRequireRawIsa = true;
            // rawIsaIsInherited 表示继承的是纯指针
            rawIsaIsInherited = true;
        }
        // 如果纯指针标记位true
        if (instancesRequireRawIsa) {
            // 递归设置类和子类的isa为纯指针,此处 rawIsaIsInherited 为打印标记位
            cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
        }
    }
// SUPPORT_NONPOINTER_ISA
#endif
复制代码
  • 判断isa是否为纯指针。
  • 判断os_object父类纯指针,是否需要递归设置纯指针。
  • 递归设置子类isa为纯指针。

父类元类关联

// 关联父类与元类。
cls->setSuperclass(supercls);
cls->initClassIsa(metacls);
复制代码

关联父类元类。对应类的继承链isa走位图

设置成员变量及标记位

// 修正 ivar 的 offset,可能会重新创建 class_ro_t 来更新 ivar
if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

// 设置实例大小(成员变量的大小)
cls->setInstanceSize(ro->instanceSize);

// 同步标记位
if (ro->flags & RO_HAS_CXX_STRUCTORS) {
    cls->setHasCxxDtor();
    if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
        cls->setHasCxxCtor();
    }
}
复制代码
  • 在有父类的情况下,并且非元类会修正ivaroffset
  • 重新设置成员变量的大小。

methodizeClass

上面rwro赋值过程中,通过实际测试并没有方法数据(baseMethodList为空),这是因为此时的rw只是一个空的结构的地址,其中selimp还没有关联起来。

static void methodizeClass(Class cls, Class previously)
{
    bool isMeta = cls->isMetaClass();
    auto rw = cls->data();
    auto ro = rw->ro();
    auto rwe = rw->ext();

    // 加载自身的方法和属性到rwe中。
    method_list_t *list = ro->baseMethods();
    if (list) {
        // 准备并修正(排序)方法列表
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
        if (rwe) rwe->methods.attachLists(&list, 1);
    }
    // 加载属性到rwe
    property_list_t *proplist = ro->baseProperties;
    if (rwe && proplist) {
        rwe->properties.attachLists(&proplist, 1);
    }
    // 加载协议到rwe
    protocol_list_t *protolist = ro->baseProtocols;
    if (rwe && protolist) {
        rwe->protocols.attachLists(&protolist, 1);
    }

    // 如果是根元类
    if (cls->isRootMetaclass()) {
        // 添加initialize方法
        addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
    }

    // 添加当前类的方法到 unattachedCategories 表里
    objc::unattachedCategories.attachToClass(cls, cls,
                                             isMeta ? ATTACH_METACLASS : ATTACH_CLASS);

}
复制代码
  • 添加cls的方法和属性到rwe中。
  • 根元类判断,添加initialize方法。
  • 将加载好的类添加到unattachedCategories表中。

rw 和 ro 数据验证

1. 当执行验证时,发现list为空。

image.png

原因是此时的ro其实是元类的ro,元类中是没有方法的,需要对元类进行过滤

2. 再次验证

image.png

终端查看方法

image.png

注意:

  • 如果是第一次加载cls是没有任何方法修正,此处不会打印出方法。这里是因为在之前调试的时候已经存在缓存,所以可以打印出方法。
  • 如果没修正方法,会执行prepareMethodLists进行方法修正。

prepareMethodLists

static void prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
                   bool baseMethods, bool methodsFromBundle, const char *why)
{
    // 核心代码
    for (int i = 0; i < addedCount; i++) {
        method_list_t *mlist = addedLists[i];
        if (!mlist->isFixedUp()) {
            // 修正方法(排序)
            fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
        }
    }
}
复制代码
  • 其中addedCount传值为1addedLists[0]rolist
  • 如果没有修正,则执行 fixupMethodList 进行排序。

fixupMethodList

static void fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
    // dyld3可能已经标记唯一了,但没有排序
    if (!mlist->isUniqued()) {
        // 遍历方法列表
        for (auto& meth : *mlist) {
            const char *name = sel_cname(meth.name());
            // 获取sel的名字,并设置。
            meth.setName(sel_registerNameNoLock(name, bundleCopy));
        }
    }
    // 排序方法表(对sel进行地址排序)
    if (sort && !mlist->isSmallList() && mlist->entsize() == method_t::bigSize) {
        method_t::SortBySELAddress sorter;
        std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter);
    }
}
复制代码

根据nameSEL进行了修正,对应慢速消息查找的二分查找。

排序验证

在上面执行stable_sort前后,添加如下代码分别打印排序前后的方法情况。

for (auto& meth : *mlist) {
    const char *name = sel_cname(meth.name());
    // 打印方法名
    printf("排序后:%s -- %p\n", name, meth.name());
}
复制代码

结果如下:

排序前:subInstanceMethod1 -- 0x100003a16
排序前:subInstanceMethod2 -- 0x100003a29
排序前:subInstanceMethod3 -- 0x100003a3c
排序前:subInstanceMethod4 -- 0x100003a4f
排序前:subInstanceMethod5 -- 0x100003a62
排序前:init -- 0x100003906
--------------
排序后:subInstanceMethod1 -- 0x100003a16
排序后:subInstanceMethod2 -- 0x100003a29
排序后:subInstanceMethod3 -- 0x100003a3c
排序后:subInstanceMethod4 -- 0x100003a4f
排序后:subInstanceMethod5 -- 0x100003a62
排序后:init -- 0x7fff7b905a35
复制代码
  • 排序前和排序后的顺序是一样的,因为可能之前已经修正过了,或者是添加的时候就已经是正确的顺序。
  • init分配了正确的地址。

分类的探究

分类结构

创建如下分类,并查看分类结构。

@interface ZLSubObject (ZLExtend)

@property (nonatomic, strong, nullable) NSString *cate_name;

- (void)cate_instanceMethod;
+ (void)cate_classMethod;

@end

@implementation ZLSubObject (ZLExtend)

- (void)cate_instanceMethod {
    NSLog(@"%s",__func__);
}

+ (void)cate_classMethod {
    NSLog(@"%s",__func__);
}

@end
复制代码

clang查看

struct _category_t {
    const char *name;
    struct _class_t *cls;
    const struct _method_list_t *instance_methods;
    const struct _method_list_t *class_methods;
    const struct _protocol_list_t *protocols;
    const struct _prop_list_t *properties;
};
复制代码

源码查看

struct category_t {
    const char *name; // 分类名
    classref_t cls; // 所属类
    WrappedPtr<method_list_t, PtrauthStrip> instanceMethods; // 属性方法列表
    WrappedPtr<method_list_t, PtrauthStrip> classMethods; // 类方法列表
    struct protocol_list_t *protocols; // 协议
    struct property_list_t *instanceProperties; // 属性列表
    struct property_list_t *_classProperties; // 类属性列表(不常见)
    
    // 元类方法列表
    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }
    // 元类属性列表
    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
    // 元类协议列表
    protocol_list_t *protocolsForMeta(bool isMeta) {
        if (isMeta) return nullptr;
        else return protocols;
    }
};
复制代码

rwe的赋值流程

在上面执行 methodizeClass 加载类时,获取了rwe的值,但是通过打印,rwe为空。这是因为rwe在有分类的情况下会有值(出自:WWDC)。rwe的赋值是来源于rw->ext()函数。如下:

class_rw_ext_t *ext() const {
    // get_ro_or_rwe()获取rwe的内容
    return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);
}
复制代码

利用反推法,全局查找get_ro_or_rwe(),只有extAllocIfNeeded()获取的是rwe本身。

class_rw_ext_t *extAllocIfNeeded() {
    // 获取rwe
    auto v = get_ro_or_rwe();
    if (fastpath(v.is<class_rw_ext_t *>())) {
        return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
    } else {
        // 创建rwe
        return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
    }
}
复制代码

该方法判断了rwe是否存在,如果存在则获取并返回,如果不存在,则创建rwe

全局搜索 extAllocIfNeeded,只有attachCategories调用最符合。

image.png

attachCategories的调用逻辑在attachToClassload_categories_nolock中。

总结

由此分类的加载,从目前看有了三条路径:

  • realizeClassWithoutSwiftmethodizeClassattachToClassattachCategories
  • load_imagesloadAllCategoriesload_categories_nolockattachCategories
  • _read_imagesload_categories_nolockattachCategories

注意: 其中第三条,该流程的先决条件的执行了load_images之后才会执行。仅对于启动时出现的类别,将推迟到调用_dyld_objc_notify_register的第一次load_images方法之后。

* 分类加载的具体流程,后面再探究。

猜你喜欢

转载自juejin.im/post/7016991209772023838