iOS底层探究-----类的加载 中

442 阅读13分钟

前言

通过篇文章--类的加载 上分析,知道我们的代码通过编译会生成machO可执行文件,然后再通过dyld链接进入内存里面来。是如何链接进入内存里面的了?我们通过read_images底层实现,分析了其中的重要流程。最后,我们定位到了非懒加载类初始化的核心流程realizeClassWithoutSwift,接下来就是对realizeClassWithoutSwift进行深入分析。

资源准备

类的初始化

在上篇文章中,重点对核心read_images进行了分析,如创建类的总表,修复预编译阶段@selector 的混乱问题,关联类的信息,修复重映射一些没有被镜像文件加载进来的类,修复一些消息,修复没有被加载的协议,对分类的处理,还有非懒加载等等。

我们需要知道编译时生成machO可执行文件时如何存入内存中的,最后我们锁定到内的初始化 realizeClassWithoutSwift,回到源码中read_images里面,如下图:

FA7B6935-583D-4311-87FF-71E928134682.png

通过这个源码,我们可以知道,对于非懒加载类(就是实现+ load方法),要进行初始化工作。

realizeClassWithoutSwift分析

首先看下源码,比较长:

static Class realizeClassWithoutSwift(Class cls, Class previously)

{

    runtimeLock.assertLocked();

    class_rw_t *rw;
    Class supercls;
    Class metacls;

    if (!cls) return nil;
    if (cls->isRealized()) {
        validateAlreadyRealizedClass(cls);
        return cls;
    }
    ASSERT(cls == remapClass(cls));

    // fixme verify class is not in an un-dlopened part of the shared cache?
    //✅ data() - ro -> 开辟rw 
    //✅ 从mach-o文件中读取数据data,转成class_ro_t的数据结构 
    //✅ ro是在编辑阶段即确定下来的数据结构,而rw是运行时的结构,所以需要开辟rw的数据空间
    auto ro = (const class_ro_t *)cls->data();
    auto isMeta = ro->flags & RO_META;

    //✅  判断是否为元类
    if (ro->flags & RO_FUTURE) {
        // This was a future class. rw data is already allocated.
        rw = cls->data();
        ro = cls->data()->ro();
        ASSERT(!isMeta);
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        // Normal class. Allocate writeable class data. ro -> rw
        //✅ 复制ro的内容到rw里面
        rw = objc::zalloc<class_rw_t>();
        rw->set_ro(ro);
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        cls->setData(rw);//✅ 再把rw存到类里面,所以在绝大多数地方,ro和rw是相似的,除了极个别被动态处理的(动态调整原始位置,比如说:分类)
    }
    //✅ 当原始内存开辟之后,一般是不再会变化的。就像在分类里面添加方法,并不会对ro进行处理
    //✅ 真实内存地址--ro;脏内存地址--rw 
    //✅ 并不是每一个类都需要插入,而且进行修改的很少,(rw扩展会导致内存占用很多,直接复制ro),动态性扩展---rwe

    cls->cache.initializeToEmptyOrPreoptimizedInDisguise();

#if FAST_CACHE_META
    if (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif

    // Choose an index for this class.
    // Sets cls->instancesRequireRawIsa if indexes no more indexes are available

    cls->chooseClassArrayIndex();

    if (PrintConnecting) {
        _objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
                     cls->nameForLogging(), isMeta ? " (meta)" : "", 
                     (void*)cls, ro, cls->classArrayIndex(),
                     cls->isSwiftStable() ? "(swift)" : "",
                     cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
    }

    // Realize superclass and metaclass, if they aren't already.
    // This needs to be done after RW_REALIZED is set above, for root classes.
    // This needs to be done after class index is chosen, for root metaclasses.
    // This assumes that none of those classes have Swift contents,
    //   or that Swift's initializers have already been called.
    //   fixme that assumption will be wrong if we add support
    //   for ObjC subclasses of Swift classes.
    //✅ 递归,加载父类、元类的实现(类的继承链图和isa走位图)
    supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

#if SUPPORT_NONPOINTER_ISA
    if (isMeta) {
        // Metaclasses do not need any features from non pointer ISA
        // This allows for a faspath for classes in objc_retain/objc_release.
       //✅ 类的isa指针地址 和 元类的isa地址,是一样的
       cls->setInstancesRequireRawIsa();
    } else {
        // Disable non-pointer isa for some classes and/or platforms.
        // Set instancesRequireRawIsa.
        
        bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
        bool rawIsaIsInherited = false;
        static bool hackedDispatch = false;
        //✅ 环境变量设置
        if (DisableNonpointerIsa) {
            // Non-pointer isa disabled by environment or app SDK version
            instancesRequireRawIsa = true;
        }
        else if (!hackedDispatch  &&  0 == strcmp(ro->getName(), "OS_object"))
        {
            // hack for libdispatch et al - isa also acts as vtable pointer
            hackedDispatch = true;
            instancesRequireRawIsa = true;
        }
        else if (supercls  &&  supercls->getSuperclass()  &&
                 supercls->instancesRequireRawIsa())
        {
            // This is also propagated by addSubclass()
            // but nonpointer isa setup needs it earlier.
            // Special case: instancesRequireRawIsa does not propagate
            // from root class to root metaclass

            instancesRequireRawIsa = true;
            rawIsaIsInherited = true;
        }

        if (instancesRequireRawIsa) {
            cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
        }
    }
// SUPPORT_NONPOINTER_ISA
#endif

    // Update superclass and metaclass in case of remapping
    //✅ 建立链表关系
    cls->setSuperclass(supercls);
    cls->initClassIsa(metacls);

    // Reconcile instance variable offsets / layout.
    // This may reallocate class_ro_t, updating our ro variable.
    if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

    // Set fastInstanceSize if it wasn't set already.
    cls->setInstanceSize(ro->instanceSize);

    // Copy some flags from ro to rw
    if (ro->flags & RO_HAS_CXX_STRUCTORS) {
        cls->setHasCxxDtor();
        if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
            cls->setHasCxxCtor();
        }
    }

    // Propagate the associated objects forbidden flag from ro or from
    // the superclass.
    if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
        (supercls && supercls->forbidsAssociatedObjects()))
    {
        rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
    }

    // Connect this class to its superclass's subclass lists
    //✅ 建立类 子类的双向链表关系
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

    // Attach categories
    methodizeClass(cls, previously);

    return cls;
}
  • 通过ro生成rw数据。
  • 关联元类与父类。这里会递归父类和元类的realizeClassWithoutSwift,最后与cls关联
  • isa的判断处理。
  • 调整ivarsoffset
  • 同步flags的标志位给rw(实际上是从ro中存储到缓存中)。
  • 关联子类与相邻的类。
  • 分类的处理逻辑在methodizeClass中。

源码细节分析

、先是相关变量的声明,判断类是否已经实现,如果已经实现,就直接对rw进行赋值处理,然后直接返回。

A0877154-8421-446B-AB7A-3B91F62F9563.png

0BBDD8A4-F750-4413-8EA1-DF7DE8EE7D91.png 因为在后面的流程中,会进行递归实现父类元类根类的父类是nil根元类的isa指向根类,所以这样可以保证类只会被初始化一次。

②、生成rw数据。在此流程中,从machO中获取的数据地址,根据class_ro_t格式进行强制,同时初始化rw的空间,并复制一份ro的数据放入rw中:

  • 首先把cls->data()赋给ro,在之前的class结构探索的时候data()获取的是rw数据。这是因为rw还没有赋值,从macho中读取__objc_classlist就存在了data()中了。具体可以在赋值前后验证cls->data()的地址。

  • 先给rw开辟空间,再将ro链接到rwro()中。(这里只是将地址给了rw,并没有完全复制一份,苹果对这块做了对应的优化。这里可以参考资源里面的WWDC2020视频。

  • 设置rwflags(uint32_t类型),31位表示rw是否已经初始化完毕(RW_REALIZED==1<<31),19位表示rw是否初始化中(RW_REALIZING==1<<19),0位表示是否是元类(元类为1,非元类为0)。

  • 最后将rw存进clsdata中。

6A61CA88-4168-4174-8D50-832499F9545A.png

这里涉及到clean memorydirty memory的概念。

  • ro属于clean memory,当在编译后,就确定了内存空间,基本不会变动,只读。加载后不会发生改变的内存空间,其中包含了类名称方法协议实例变量等相关信息;

  • rw属于dirty memoryrw是运行时的结构,内存空间可以变动,可读可写,由于其动态性,可以在运行时,向类里面动态添加属性方法协议。所以在运行时会发生变更的内存空间大小。

  • rwe属于类的额外信息。在rw中,只有不到10%的类真正的更改了他们的方法,并不是每一个类都需要插入数据,进行修改的类很少,避免资源的消耗,所以就有了rwe

  • 注1:这里有个细节是调试代码中判断了isMeta,因为元类和类的名称相同。否则元类也会进入这块逻辑。

  • 注2 在源码里面,有auto ro = (const class_ro_t *)cls->data(); ,为什么 ro 是由 cls->data() 进行赋值了? 我们看一下 data() 的实现,它是一个class_rw_t类型,而class_rw_t里面,就包含了类的众多信息,如:protocolspropertiesmethodsdeepCopyextAllocIfNeededextflagswitnessindex。。。等等。

class_rw_t *data() const {
        return bits.data();
    }

cls->data()读取出来是一个地址指针,而这个指针根据指定的格式 class_ro_t,找到其对应的类型class_rw_t,拿到这个类型后,就能对class_rw_t里面的内容挨个进行赋值。除非当前的这个格式,得到的地址无法解析到位,就好比通过这个地址所得到的内容,和原始编排的格式不匹配,所以就会解析不出来。

所以,ro 一旦从 cls->data() 也就是 machO 里面读到地址指针,就能根据其相对格式class_ro_t,对class_rw_t里面的内容进行一一赋值。

那么接着往下走。

通过lldb调试跟踪下, 011C71A4-A3E9-43F5-B811-E196DEA9FF5C.png

根据打印结果,LGPerson 里面定义的方法都没有被查出来,只能获取LGPerson类的地址,但是其里面的所有信息都没有。还没被调用。sel和imp还没有形成一个绑定关系;

接着就是 rw 开辟空间,ro 赋值给 rw,再把 rw 赋值给 clsdata。这里是把干净内存 ro 复制一块到 rw 里。这也是很多地方看到 rwro 数据是相似的,除非一些动态化处理的地方(比如说 addmethodaddpropertycategory),这也是为什么 ro 在最原始的时候开辟完之后就不会再变了。因为不会直接对 ro 进行处理。

③、cls关联元类父类,通过递归处理,进行父类和元类的实现,如下图:

EC9C61C0-AB80-4A6D-8075-7E43BD9C97C4.png

与下面的cls->setSuperclass(supercls);cls->initClassIsa(metacls);相呼应。

④、isa的判断处理:

9AD0BB09-2DBC-4634-AFAE-93C1B97FC921.png 判断设置isa是否纯指针。

  • 元类isa是纯指针。
  • 类的isa是否纯指针取值flags(FAST_CACHE_REQUIRES_RAW_ISA 1<<13)第13位。
  • DisableNonpointerIsa也就是OBJC_DISABLE_NONPOINTER_ISA环境变量配置来配置是否纯指针。
  • 父类是纯指针,并且父类还有父类。那么自己也要是纯指针。rawIsaIsInherited (只是控制打印)表示继承的是纯指针。
  • 递归设置isa为纯指针,子类也设置为纯指针。(父类为纯指针,子类也为纯指针)。

⑤、关联父类与元类,也就是继承链isa走位

084D794F-1AE8-4A2E-A311-DC4398432617.png

⑥、调整 ivaroffset

131502BD-CF5F-4B46-BEE9-ABC56AEBCBCF.png

  • 在有父类并且非元类的情况下,会进行ivar offset的调整,在reconcileInstanceVariables实现。
  • 重新设置成员变量的大小。在setInstanceSize中实现,其中有对常量的修改。类实例对象的大小设置-在对象初始化的时候会用到

⑦、rw 同步 ro 标志位

9059C37C-CD2D-4D1B-A9A8-7E694DACBFBD.png

  • 拷贝roflagsrw中(其实是放在缓存中)。
  • RO_HAS_CXX_STRUCTORS (1<<2) flags2位标记c++构造方法。
  • RO_HAS_CXX_DTOR_ONLY (1<<8) flags8位标记c++析构方法。
  • 本类禁止关联对象或者父类禁止关联对象,则同步RW_FORBIDS_ASSOCIATED_OBJECTS (1<<20)标记。flags20位标记是否禁止关联对象。

⑧、建立父类、子类的双向链表关系

B125AF62-8E61-4EFF-9FF2-A6F9AC477AB3.png

  • addSubclass----关联父类的子类。同时也关联子类的相邻类以及c++构造和析构的标记。同时根据父类是否isa纯指针同步给子类。

  • addRootClass关联根类,在这个流程中NSObject的相邻类会被设置为nil_firstRealizedClass会被设置为NSObject

  • addSubclass详情 95D2E676-8EDA-4C21-BEF7-672B6AA1180D.png

  • addRootClass详情

2600FBEE-DFAC-417C-9DAF-A7EF51F5B9C4.png

⑨、方法化当前的类,向类中添加方法,协议、属性,同时对方法列表进行排序等操作:

955D4E53-FD87-46CE-B57A-55BBDA24042E.png

然后当执行到 methodizeClass 函数之前,打印 cls 的地址,还是打印不出来,但是我们在前面的分析中,可以知道rorw是进行了赋值操作的,然而在这里却打印不出来,这说明了在methodizeClass之前,并没有对 rorw 进行处理,打印结果如下图:

D1335A90-6A5B-4F69-B23B-3F2B532B8E63.png

那么接下来,我们就要对methodizeClass函数进行详细分析了。

methodizeClass分析

先看下源码:

static void methodizeClass(Class cls, Class previously) {

    runtimeLock.assertLocked();
    bool isMeta = cls->isMetaClass();
    auto rw = cls->data();
    auto ro = rw->ro();
    auto rwe = rw->ext();

    // Methodizing for the first time
    if (PrintConnecting) {
        _objc_inform("CLASS: methodizing class '%s' %s", 
                     cls->nameForLogging(), isMeta ? "(meta)" : "");
    }

    // Install methods and properties that the class implements itself.
    //✅ 获取ro方法列表
    method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
        if (rwe) rwe->methods.attachLists(&list, 1);
    }

    //✅ 获取ro属性列表
    property_list_t *proplist = ro->baseProperties;
    if (rwe && proplist) {
        rwe->properties.attachLists(&proplist, 1);
    }

    //✅ 获取协议列表
    protocol_list_t *protolist = ro->baseProtocols;
    if (rwe && protolist) {
        rwe->protocols.attachLists(&protolist, 1);
    }

    // Root classes get bonus method implementations if they don't have 
    // them already. These apply before category replacements.
    //✅ 是否根元类,根元类加了initialize方法。
    if (cls->isRootMetaclass()) {
        // root metaclass
        addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
    }

    // Attach categories.
    //✅ 分类的处理
    if (previously) {
        if (isMeta) {
objc::unattachedCategories.attachToClass(cls, previously, ATTACH_METACLASS);
        } else {
            // When a class relocates, categories with class methods
            // may be registered on the class itself rather than on
            // the metaclass. Tell attachToClass to look for those.
            objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_CLASS_AND_METACLASS);
        }
    }
    objc::unattachedCategories.attachToClass(cls, cls, isMeta ? ATTACH_METACLASS : ATTACH_CLASS);

#if DEBUG
    // Debug: sanity-check all SELs; log method list contents
    for (const auto& meth : rw->methods()) {
        if (PrintConnecting) {
            _objc_inform("METHOD %c[%s %s]", isMeta ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(meth.name()));
        }
        ASSERT(sel_registerName(sel_getName(meth.name())) == meth.name());
    }
#endif
}

刚开始进入方法里面,rwe 的值是为 NULL 的。

  • 对方法的attachLists处理。(当rwe为空时,不执行)
  • 对属性的attachLists处理。(当rwe为空时,不执行)
  • 对协议的attachLists处理。(当rwe为空时,不执行)
  • 对根元类添加initialize方法。
  • 对分类的attachToClass处理。(最终不会走进attachCategories逻辑)

prepareMethodLists 分析

通过断点调试发现,刚开始时,虽然能获取方法列表 list 的地址,但是并不能拿到里面的方法,如下图:

7AE67FA9-1001-48DB-BD80-F78010E9985D.png

也就是说,方法列表并没有被处理,放入ro里面,而是要通过prepareMethodLists函数进行处理,查看该函数源码:

1F7F6702-ECA0-4EB0-AEC9-87D299383E55.png

fixupMethodList分析

核心流程,fixupMethodList,根据注释:根据需要对selector进行修复。进入fixupMethodList方法,查看实现流程。见下图:

D8690428-4B3C-4535-B8E9-6ECE8FF3E6E1.png

  • 先对methodname也就是SEL进行了修正。sel_registerNameNoLock -> __sel_registerName -> search_builtins->_dyld_get_objc_selector。相当于以dyld的为准。

  • 接着对methodList进行了排序(按照SEL的地址),这里就与慢速消息查找的二分查找对应上了。排序是在这里进行的。

  • 最后设置标志位。

  • : 根据注释// Don't try to sort small lists, as they're immutable,说明small 类型的,不可变。

打印排序情况,如下图:

782F1E1E-FEE9-4E85-A3AE-7AE6602CB141.png

  • 方法的顺序默认是按照编译时候的顺序排序的,一般情况下是有序的。(文件中方法的顺序)

  • 在进行dyld修正SEL地址后需要重新排序。

在排序前,方法地址大小有点错乱,当排序后,方法地址大小就从小到大排序。我们在前面的博客里面分析方法慢速查找的时候,里面就有二分法查找,里面就有排序。

分类的本质

prepareMethodLists执行完成后,因为rwe为空,所以后续的attachLists不会执行。根据之前WWDC的介绍rwe在有分类的情况下会出现,那么就加个分类,如下图:

0BB2753C-DCD3-44C8-9788-CBE1E6A1B34C.png

通过 clang 命令,还原成C++代码,打开main.cpp文件,可以知道分类在底层的实现,如下图:

666A7914-51DF-43B2-AB73-09CEF8C01573.png

_CATEGORY_LGPerson_$_LG_category_t 类型:

49D245F2-6ECD-49EB-A82F-86789F63E267.png

根据 main.cpp 文件,可以知道:

  • 分类也是一个结构体类型;
  • name就是分类的名字,就是LG
  • cls就是 LGPerson 类;
  • 在类中只有一个methods,在分类中有了instance_methodsclass_methods。因为分类是没有元类的(也就是没有分元类)。
  • 分类中是有properties的。

再看下赋值情况,此时还只是静态编译阶段,没有到达运行时,所以还没有真正的赋值,现在只是占位的随机值,但是也能看出一二来,如下图:

3DB8A782-3296-4E44-9579-3EF9772C9F51.png

遵循的协议:

5D745D82-2786-495E-9EA0-795A07FD71E3.png

这里是NSObject协议。

属性赋值:

DEDF0BC4-8F57-445D-B153-AA51C6859803.png

但是并没有属性所对应的 setget 方法。

方法列表: 36F52C0F-AEDF-4800-AD5F-FB31F218F8E9.png

所以只能通过关联对象处理。

看了分类的 category_tc++ 文件里面的实现,那么在objc源码中,是否有对应的代码段了?那么直接到源码中去查找。

category_t 源码分析

看源码:

A64CC6BA-9BDF-44BF-BB64-94A049F01CE5.png

  • 在源码中category_tclang转换的c++代码里面的category_t相比,两者之间的结构有所不同。
  • 源码中的category_t支持类属性---- _classProperties。但是在 c++ 文件里面却没有。

categoryXcode 文档探索

通过快捷键 command + shift + 0 进入到文档里面(先打开Xcode,再使用快捷件),在文档里面搜索 Category,就能找到对应的位置,如下图:

4F2C887C-6A25-49FF-ABB8-45EF52C70AD5.png

在文档中,发现categoryobjc_category 类型的,那么可以在objc源码中,找找:

8C19B087-0FC5-4505-8E87-4B7790689D6F.png

我们分析了分类的本质,知道分类的构成情况,在分类里面,无论是方法、属性、协议等,都在分类的结构体里面,那么是怎么被类所调用的了?

接下来继续回到objc源码中我们所分析的methodizeClass函数里面。

分类的加载

通过类的加载源码分析,方法、属性、协议等通过attachLists来实现,分类通过attachToClass来实现。他们的控制条件是rwe。而rwe来源于rw->ext()

11E58DB4-EACE-4A67-A73B-5E9BF3376A94.png

进入到 ext,查看实现:

AD06C977-894C-431C-99C9-8DB49C530170.png

  • 当存在时,直接去 rwe, 当不存在的话,就直接创建。

那么就可以在源码中,查看下 extAllocIfNeeded 的调用情况,就知道使用 extAllocIfNeeded 的限制条件了。下面就是在源码中的调用情况:

  • attachCategories中调用。
  • demangledName的判断isRealized()或者isFuture()进行取值。
  • class_setVersion类的版本设置,调用了。
  • addMethods_finish调用。
  • class_addProtocol调用。
  • _class_addProperty调用。
  • objc_duplicateClass调用。

也就是在动态运行时,再有 rwe 的动态创建和处理。

那么分类是什么时候加载的了?

attachCategories 初探索分析

methodizeClass 中,根据源码的注释和我们自己的分析,有一个初步的探索路线,由 methodizeClass 函数:

8A3D0AF9-A83E-45EB-B5C8-2B0539AB32F6.png

再到 attachToClass 里面的实现:

02B91245-5352-4C3D-A3F0-2AD2F4EB96BD.png

最后到 attachCategories的具体实现:

// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order, 
// oldest categories first.

static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,int flags)
{

    if (slowpath(PrintReplacedMethods)) {
        printReplacements(cls, cats_list, cats_count);
    }
    if (slowpath(PrintConnecting)) {
        _objc_inform("CLASS: attaching %d categories to%s class '%s'%s",
                     cats_count, (flags & ATTACH_EXISTING) ? " existing" : "",
                     cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : "");
    }
    
    /*
     * Only a few classes have more than 64 categories during launch.
     * This uses a little stack, and avoids malloc.
     *
     * Categories must be added in the proper order, which is back
     * to front. To do that with the chunking, we iterate cats_list
     * from front to back, build up the local buffers backwards,
     * and call attachLists on the chunks. attachLists prepends the
     * lists, so the final result is in the expected order.
     */
    constexpr uint32_t ATTACH_BUFSIZ = 64;
    method_list_t   *mlists[ATTACH_BUFSIZ];
    property_list_t *proplists[ATTACH_BUFSIZ];
    protocol_list_t *protolists[ATTACH_BUFSIZ];

    uint32_t mcount = 0;
    uint32_t propcount = 0;
    uint32_t protocount = 0;
    bool fromBundle = NO;
    bool isMeta = (flags & ATTACH_METACLASS);
    auto rwe = cls->data()->extAllocIfNeeded();

    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, __func__);
                rwe->methods.attachLists(mlists, mcount);
                mcount = 0;
            }
            mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
            fromBundle |= entry.hi->isBundle();
        }
        
        property_list_t *proplist =
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            if (propcount == ATTACH_BUFSIZ) {
                rwe->properties.attachLists(proplists, propcount);
                propcount = 0;
            }
            proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
        }

        protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
        if (protolist) {
            if (protocount == ATTACH_BUFSIZ) {
                rwe->protocols.attachLists(protolists, protocount);
                protocount = 0;
            }
            protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
        }
    }

    if (mcount > 0) {
        prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle, __func__);
        rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
        if (flags & ATTACH_EXISTING) {
            flushCaches(cls, __func__, [](Class c){
                // constant caches have been dealt with in prepareMethodLists
                // if the class still is constant here, it's fine to keep
                return !c->cache.isConstantOptimizedCache();
            });
        }
    }

    rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
    rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}

结合上文的分析,可以看到除了attachCategories,其它的代码块逻辑,要么是对类进行动态处理,要么是修复类的时候创建rwe。那么显然核心逻辑就在attachCategories了。

而对attachCategories有调用操作的,就是attachToClass(刚刚上面图片中就有)与load_categories_nolock,如下图:

0E2F0DCF-0416-4485-958C-4048D38CAA22.png

  • attachToClass是在methodizeClass中调用的
  • 通过全文索引load_categories_nolock,知道其是在_read_images(这里不会调用)与loadAllCategories中调用。
  • loadAllCategories还是在load_images的时候加载。

所以分类的加载就有了两条线路:

  1. methodizeClass -> attachToClass -> attachCategories
  2. load_images -> loadAllCategories -> load_categories_nolock -> attachCategories

那么分类具体加载的详情是怎样的了?将在下篇文章分析。

总结

  • realizeClassWithoutSwift
    • 通过rorw赋值,生成rw数据。这里rw只是关联到了ro
    • isa判断处理,关联元类与父类。
      • 元类isa是纯指针。
      • isa是递归设置的,父类为纯指针,子类也为纯指针。
    • 调整ivarsoffset
    • 同步flags的标志位给rw(实际上是从ro中存储到缓存中)。
    • 关联子类(firstSubclass)与相邻的类(nextSiblingClass)。
    • methodizeClass(主要是对方法列表进行排序 & 加载分类 & rwe 的处理)
      • prepareMethodLists
        • fixupMethodList 修正并且排序方法列表(ro的)
          • sel_registerNameNoLock最终执行_dyld_get_objc_selectorSEL地址修复为dyld提供的。
          • SortBySELAddress对方法列表进行排序。
      • attachToClass分类的加载。
  • rwe
    • rwe是在extAllocIfNeeded中创建的。
      • 加载分类。
      • 动态修改类(addMethods_finishclass_addProtocol_class_addProperty)。
    • 修复类(demangledNameclass_setVersionobjc_duplicateClass)。
  • 分类的加载有两条路径:
    • methodizeClass -> attachToClass -> attachCategories
    • load_images -> loadAllCategories -> load_categories_nolock -> attachCategories