阅读 490

iOS 底层原理探索 之 类的加载

写在前面: iOS底层原理探究是本人在平时的开发和学习中不断积累的一段进阶之
路的。 记录我的不断探索之旅,希望能有帮助到各位读者朋友。
复制代码

目录如下:

  1. iOS 底层原理探索 之 alloc
  2. iOS 底层原理探索 之 结构体内存对齐
  3. iOS 底层原理探索 之 对象的本质 & isa的底层实现
  4. iOS 底层原理探索 之 isa - 类的底层原理结构(上)
  5. iOS 底层原理探索 之 isa - 类的底层原理结构(中)
  6. iOS 底层原理探索 之 isa - 类的底层原理结构(下)
  7. iOS 底层原理探索 之 Runtime运行时&方法的本质
  8. iOS 底层原理探索 之 objc_msgSend
  9. iOS 底层原理探索 之 Runtime运行时慢速查找流程
  10. iOS 底层原理探索 之 动态方法决议
  11. iOS 底层原理探索 之 消息转发流程
  12. iOS 底层原理探索 之 应用程序加载原理dyld (上)
  13. iOS 底层原理探索 之 应用程序加载原理dyld (下)

以上内容的总结专栏


细枝末节整理


前言

在前两篇文章内容中,我们探索了应用程序的加载,是通过dyld去链接镜像文件images,映射到我们的程序中。但是,也仅仅是做了映射的工作,并没有装载到内存中去。比如我们有一个类,类中有方法、协议等等一些内容,这些内容需要加载到内存中去,我们才可以将类实例化,并调取其方法。

我们知道链接的镜像文件是machO格式的,那么如何通过machO中的地址将其读取到内存中去呢?最好是有一张表能够来存储所有的类信息,然后类的初始化,其内部数据的 rorw 初始化。 对与这段猜想不知是否正确,接下来我们就一步步验证,看类的加载究竟是怎样的一个流程。

_objc_init

上一篇章在加载images的时候是在 _objc_init 中调用了 _dyld_objc_notify_register 所以,我们从 _objc_init 开始,探索下在 _dyld_objc_notify_register 之前的相关 init:

/***********************************************************************
* _objc_init
* 引导程序初始化,用dyld注册我们的镜像通知程序
* 在库初始化时间之前被libSystem调用
**********************************************************************/

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // 修复延迟初始化直到找到一个使用objc的镜像?
    // 环境变量的初始化
    environ_init();
    // 线程局部存储的初始化
    tls_init();
    // 运行c++静态构造函数,Libc在dyld调用静态构造函数之前调用_objc_init(), 所以我们得自己做
    static_init();
    // 初始化了两整表 unattachedCategories,allocatedClasses
    runtime_init();
    // 初始化libobjc的异常处理系统
    exception_init();
#if __OBJC2__
    cache_t::init();
#endif
    //启动回调机制。通常这样不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待地加载 trampolines dylib
    _imp_implementationWithBlock_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}
复制代码

_dyld_objc_notify_register(&map_images, load_images, unmap_image)

  • 这个函数中为什么 map_images需要取地址,load_images并不需要呢?
  1. 取地址在这里是指针传递,这样这里的函数能够和内部调用的函数指针同步发生变化,因为这里的map_images很重要,它要开始映射整个镜像文件,是一个耗时的过程,如果这个过程没有发生同步变化,就会发生错乱,所以要保持地址的同步变化;
  2. load_iamges 只是调用 load 方法而已。

map_images

/***********************************************************************
* map_images
* 处理dyld映射到的给定镜像
* 在使用特定于abi的锁后调用与abi无关的代码
*
* 锁定:写锁runtimeLock
**********************************************************************/
void
map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    mutex_locker_t lock(runtimeLock);
    return map_images_nolock(count, paths, mhdrs);
}
复制代码

很简单,两行代码我们来到了 map_images_nolock

map_images_nolock

/***********************************************************************
* map_images_nolock
* 处理dyld映射到的给定镜像
* 所有的类注册和修正都被执行(或延迟等待)
* 发现丢失的超类等,并调用+load方法
*
* Info[]是自下而上的顺序,即libobjc将在早些时候
* 数组比任何链接到libobjc的库
*
* 锁定:loadMethodLock(旧的)或runtimeLock(新的)由map_images获取
**********************************************************************/
#if __OBJC2__
#include "objc-file.h"
#else
#include "objc-file-old.h"
#endif

void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    static bool firstTime = YES;
    header_info *hList[mhCount];
    uint32_t hCount;
    size_t selrefCount = 0;

    // 如果需要,执行首次初始化
    // 该函数在普通库初始化器之前调用
    // 修复延迟初始化直到找到一个使用objc的镜像?
    if (firstTime) { ... }
    
    if (PrintImages) { ... }
    
    // 找到所有带有Objective-C元数据的镜像
    hCount = 0;

    // Count classes. Size various table based on the total.
    int totalClasses = 0;
    int unoptimizedTotalClasses = 0;
    {
        uint32_t i = mhCount;
        while (i--) { ... }
    }
    
    //  执行必须延迟到的一次性运行时初始化 
    //  可执行文件本身被找到。这需要提前完成
    //  进一步的初始化
    //  可执行文件可能不在这个infoList中,如果

    //  可执行程序不包含Objective-C代码,而是Objective-C
    //  稍后动态加载
    if (firstTime) { ... }
    
    
    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }

    firstTime = NO;
    
    // 在一切设置完成后调用镜像加载函数
    for (auto func : loadImageFuncs) {
        for (uint32_t i = 0; i < mhCount; i++) {
            func(mhdrs[i]);
        }
    }
复制代码

首次初始化完成之后, 开始 _read_images

_read_images

/***********************************************************************
* _read_images
* 对链接中的头执行初始处理 
* 以headerList开头的列表
*
*  称为:map_images_nolock
*
* 锁定:由map_images获取的runtimeLock
**********************************************************************/
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
    header_info *hi;
    uint32_t hIndex;
    size_t count;
    size_t i;
    Class *resolvedFutureClasses = nil;
    size_t resolvedFutureClassCount = 0;
    static bool doneOnce;
    bool launchTime = NO;
    TimeLogger ts(PrintImageTimes);

    runtimeLock.assertLocked();

#define EACH_HEADER \
    hIndex = 0;         \
    hIndex < hCount && (hi = hList[hIndex]); \
    hIndex++

    // 1、条件控制进行一次的加载 找到一张全部的表
    if (!doneOnce) { ... }
    
    // 2、修复 @selector 引用
    // sel 名字 + 地址 
    // 一个是machO中的地址(有相对偏移地址),一个是dyld加载的地址(需要以此为基准)需要进行修复(重定向)
    static size_t UnfixedSelectors;
    { ... }
    
    ts.log("IMAGE TIMES: fix up selector references");

    // 3、发现类,修复未解决的未来类
    bool hasDyldRoots = dyld_shared_cache_some_image_overridden();

    for (EACH_HEADER) { ... }
    
    ts.log("IMAGE TIMES: discover classes");

    // 4、修复重新映射的类
    // 类列表和非惰性类列表保持未重映射
    // 类引用和超级引用被重新映射以进行消息分派
    
    if (!noClassesRemapped()) { ... }
    
    ts.log("IMAGE TIMES: remap classes");

#if SUPPORT_FIXUP
    // 5、修复旧的objc_msgSend_fixup调用站点
    for (EACH_HEADER) { ... }
    
    ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
#endif


    // 6、发现协议,修复协议参考
    for (EACH_HEADER) { ... }
    
    ts.log("IMAGE TIMES: discover protocols");

    // 7、修复 @protocol 引用
    // 预优化图像可能有此权限 
    // 已经回答了,但我们不确定
    for (EACH_HEADER) { ... }
    
    ts.log("IMAGE TIMES: fix up @protocol references");

    // 8、发现类别,只有在初始类别之后才这样做
    // 附件已完成,对于在启动时出现的类别
    // 发现被延迟到第一次load_images调用之后
    // 对_dyld_objc_notify_register的调用完成 
    if (didInitialAttachCategories) { ... }
    
    ts.log("IMAGE TIMES: discover categories");

    // 9、类别发现必须晚,以避免潜在的竞赛
    // 当其他线程之前调用新的类别代码时
    // 这个线程完成它的修复

    // 10、 +加载由prepare_load_methods()处理
    // 实现非惰性类(用于 实现了 +load 方法的静态实例)
    for (EACH_HEADER) { ... } 
    
    ts.log("IMAGE TIMES: realize non-lazy classes");

    // 实现新解析的未来类,以防CF操纵它们
    if (resolvedFutureClasses) { ... }
    
    ts.log("IMAGE TIMES: realize future classes");

    if (DebugNonFragileIvars) {
        realizeAllClasses();
    }
    
    // 打印过程中统计
    if (PrintPreopt) { ... } 
    
#undef EACH_HEADER
}
复制代码

到这里,其实很明显了: map_images 管理文件中和动态库中的所有符号 (class protocol selector category)。 上面的第十条就是关于类的加载。

if (!doneOnce) { ... } 条件控制进行一次的加载

    // 带标记的指针混淆器旨在使其更加困难 要让攻击者构造一个特定对象作为标记指针, 在存在缓冲区溢出或其他写控制的情况下 内存。当设置时,混淆器与带标记的指针xor 或检索有效负载值。它们一开始就充满了随机性 使用
    initializeTaggedPointerObfuscator();
    
    // namedClasses
    // 预优化的类不在这个表中
    // 4/3是NXMapTable的负载因子
    int namedClassesSize = 
            (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
    // 这是一个错误的名称:gdb_objc_realized_classes实际上是 命名类不在dyld共享缓存中,无论是否实现。 此列表排除了必须查找的惰性命名类 使用getClass钩子
    gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);

        ts.log("IMAGE TIMES: first time tasks");
复制代码

for (EACH_HEADER) { ... } 发现类,修复未解决的未来类

当前在 read_images 环节,从镜像文件中读取machO的格式到表里边,然后通过地址还原到类里面,所以如何还原到类里面是该流程的重点,也就是 readClass部分:

for (EACH_HEADER) {
    if (! mustReadClasses(hi, hasDyldRoots)) {
        // 图像已经充分优化,我们不需要调用readClass()
        continue
    }

    classref_t const *classlist = _getObjc2ClassList(hi, &count);

    bool headerIsBundle = hi->isBundle()
    bool headerIsPreoptimized = hi->hasPreoptimizedClasses();

    for (i = 0; i < count; i++) {

        // 从machO的数据表中读取到符号地址
        Class cls = (Class)classlist[i];
        // 赋值类的名字,进行类的关联处理
        Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);

        if (newCls != cls  &&  newCls) {
            // 类被移动但没有被删除,目前这种情况发生
            // 只有当新阶级解决了未来的阶级
            // 不懒认识下面的类
            resolvedFutureClasses = (Class *)
                realloc(resolvedFutureClasses,                          (resolvedFutureClassCount+1) * sizeof(Class));
            resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
        }
    }
}
复制代码

readClass

/***********************************************************************
* readClass
* 读取编译器编写的类和元类
* 返回新的类指针。这可能是: 
* - cls
* - nil  ( cls 缺少一个弱链接超类 )
* - something else ( 这个类的空间被未来的类保留 )
*
* 注意,该函数执行的所有工作都是由preflightby执行的
* mustReadClasses(). 不要更改这个函数而不更新那个函数
*
* 锁定:由map_images或objc_readClassPair获取的runtimeLock
**********************************************************************/
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    const char *mangledName = cls->nonlazyMangledName();

    // 此处我们只匹配到自己添加的类来断点研究其 readClass 流程
    if (0 == strcmp(mangledName, "SMPerson")) {
        printf("\nSM 我们自己添加的类 需要重点关注的 -- %s - %s", __func__ , mangledName);
    }
    
    // nil  ( cls 缺少一个弱链接超类 )
    if (missingWeakSuperclass(cls)) { ... }
    
    cls->fixupBackwardDeployingStableSwift();

    Class replacing = nil;
    if (mangledName != nullptr) {
        if (Class newCls = popFutureNamedClass(mangledName)) {
            // 这个名称之前被分配为一个未来的类
            // 复制objc_class到future class的结构
            // 保留未来的rw数据块

            if (newCls->isAnySwift()) {
                _objc_fatal("Can't complete future class request for '%s' "
                            "because the real class is too big.",
                            cls->nameForLogging());
            }

            class_rw_t *rw = newCls->data();
            const class_ro_t *old_ro = rw->ro();
            memcpy(newCls, cls, sizeof(objc_class));

            // 手动设置地址区分的ptrauthed字段
            // 以便newCls获得正确的签名
            newCls->setSuperclass(cls->getSuperclass());
            newCls->initIsa(cls->getIsa());

            rw->set_ro((class_ro_t *)newCls->data());
            newCls->setData(rw);
            freeIfMutable((char *)old_ro->getName());
            free((void *)old_ro);

            addRemappedClass(cls, newCls);

            replacing = cls;
            cls = newCls;
        }
    }
    
    if (headerIsPreoptimized  &&  !replacing) {
        // 共享缓存中内置的类列表
        // Fixme严格断言因为重复而不能工作
        // ASSERT(cls == getClass(name));
        ASSERT(mangledName == nullptr || getClassExceptSomeSwift(mangledName));
    } else {
        if (mangledName) { //some Swift generic classes can lazily generate their names
            addNamedClass(cls, mangledName, replacing);
        } else {
            Class meta = cls->ISA();
            const class_ro_t *metaRO = meta->bits.safe_ro();
            ASSERT(metaRO->getNonMetaclass() && "Metaclass with lazy name must have a pointer to the corresponding nonmetaclass.");
            ASSERT(metaRO->getNonMetaclass() == cls && "Metaclass nonmetaclass pointer must equal the original class.");
        }
        addClassTableEntry(cls);
    }
    
    // 供以后参考:共享缓存从不包含MH_BUNDLEs
    if (headerIsBundle) {
        cls->data()->flags |= RO_FROM_BUNDLE;
        cls->ISA()->data()->flags |= RO_FROM_BUNDLE;
    }
    
    return cls;
}
复制代码

readClass 方法中在 if (mangledName != nullptr) { if (Class newCls = popFutureNamedClass(mangledName)) { 分支中,我们看到了有对类的 rorw 的相关赋值,但是断点调试下来, 并不会执行到此分支。而是来到了之后到 if (headerIsPreoptimized && !replacing) { } else 的流程里面来:

// 添加name => cls到命名的非元类映射;警告重复的类名并保留旧的映射
// 将 类名 和 类地址 加到 gdb_objc_realized_classes 表里面做关联
addNamedClass(cls, mangledName, replacing);
// 向所有类的表中添加一个类。如果addMeta为true;自动添加类的元类
addClassTableEntry(cls);
复制代码

但是,此时并没有将我们关注的类中的rorw 数据,所以其赋值操作并不在此方法内部实现。但是有关联类名和类地址。

接下来,我们在 _read_images 流程中继续向下,发现关于类的相关操作的还有几处,跟着断点走下来,来到了 realizeClassWithoutSwift 内:

realizeClassWithoutSwift - { ro rw superClass isa }

/***********************************************************************
* realizeClassWithoutSwift
* 对类cls执行首次初始化
* 包括分配它的读写数据
* 没有执行任何swift端初始化
* 返回类的实际类结构 
* 锁定:runtimeLock必须被调用者写锁定
**********************************************************************/
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));

    // 修复验证类不在共享缓存的未打开部分?

    // 此处开辟了ro的内存空间地址,其内部并没有加载内容数据
    // 举个例子:你买了个房子,现在只是写上了你的名字,但是房子里还是空着的,可能你去家具城定好了家具,但,此刻还没有布置到房子里去
    auto ro = (const class_ro_t *)cls->data();
    auto isMeta = ro->flags & RO_META;
    if (ro->flags & RO_FUTURE) {
        // 这是未来的课程。已经分配了Rw数据
        rw = cls->data();
        ro = cls->data()->ro();
        ASSERT(!isMeta);
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        // 正常的类。分配可写的类数据
        // 将干净的内存 ro 复制一份到 rw
        rw = objc::zalloc<class_rw_t>();
        rw->set_ro(ro);
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        cls->setData(rw);
    }
    
    cls->cache.initializeToEmptyOrPreoptimizedInDisguise();

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

    // 为这个类选择一个索引
    // 如果索引没有更多的索引可用,设置cls->instancesRequireRawIsa
    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)" : "");
    }

    // 实现超类和元类,如果它们还没有实现的话
    // 对于根类,需要在上面设置rw_realize之后完成
    // 这需要在为根元类选择类索引之后进行
    // 这假设这些类都没有Swift内容
    // 或者Swift的初始化器已经被调用
    // 修正一下,如果我们添加支持,这个假设将是错误的
    // 对于Swift类的ObjC子类
    supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

#if SUPPORT_NONPOINTER_ISA
    if (isMeta) {
        // 元类不需要来自非指针ISA的任何特性
        // 这允许为objc_retain/objc_release中的类提供一个路径
        cls->setInstancesRequireRawIsa();
    } else {
        // 对于某些类和/或平台禁用非指针
        // 设置 instancesRequireRawIsa.
        bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
        bool rawIsaIsInherited = false;
        static bool hackedDispatch = false;

        if (DisableNonpointerIsa) {
            // 非指针是禁用的环境或应用程序SDK版本
            instancesRequireRawIsa = true;
        }
        else if (!hackedDispatch  &&  0 == strcmp(ro->getName(), "OS_object"))
        {
            // libdispatch等人还充当虚函数表指针
            hackedDispatch = true;
            instancesRequireRawIsa = true;
        }
        else if (supercls  &&  supercls->getSuperclass()  &&
                 supercls->instancesRequireRawIsa())
        {
            // 这也是由addSubclass()传播的
            // 但是非指针需要更早的设置
            // 特殊情况:instancesRequireRawIsa不传播
            // 从根类到根元类
            instancesRequireRawIsa = true;
            rawIsaIsInherited = true;
        }

        if (instancesRequireRawIsa) {
            cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
        }
    }
// SUPPORT_NONPOINTER_ISA
#endif
    // 更新超类和元类,以防重新映射
    cls->setSuperclass(supercls);
    cls->initClassIsa(metacls);
    
    // 协调实例变量偏移量/布局
    // 这可能会重新分配class_ro_t,更新ro变量
    if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

    // 如果还没有设置,请设置fastInstanceSize
    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();
        }
    }
    
    // 从ro或from传播关联对象禁止标志
    // the superclass.
    if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
        (supercls && supercls->forbidsAssociatedObjects()))
    {
        rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
    }

    // 将这个类连接到它的父类的子类列表
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

    // 附加类别
    methodizeClass(cls, previously);

    return cls;
}
复制代码

methodizeClass(cls, previously)

/***********************************************************************
* methodizeClass
* 修复cls的方法列表、协议列表和属性列表
* 附加任何突出类别
* 锁定:runtimeLock必须由调用者持有
**********************************************************************/
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();
    
    const char *mangledName = cls->nonlazyMangledName();

    // 第一次系统化
    if (PrintConnecting) {
        _objc_inform("CLASS: methodizing class '%s' %s", 
                     cls->nameForLogging(), isMeta ? "(meta)" : "");
    }

    // 安装类自身实现的方法和属性
    method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
        // 并没有对 rwe 赋值, 什么时候赋值的呢?
        if (rwe) rwe->methods.attachLists(&list, 1);
    }

    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);
    }

    // 如果根类没有额外的方法实现,它们会得到额外的方法实现 
    // 他们已经,这些适用于类别替换之前
    if (cls->isRootMetaclass()) {
        // 根元类
        addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
    }

    // 附加类别
    if (previously) {
        if (isMeta) {
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_METACLASS);
        } else {
            // 当一个类重新定位时,用类方法分类
            // 可以注册在课程本身而不是
            // 元类。告诉attachToClass寻找这些
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_CLASS_AND_METACLASS);
        }
    }
    objc::unattachedCategories.attachToClass(cls, cls,
                                             isMeta ? ATTACH_METACLASS : ATTACH_CLASS);

#if DEBUG
    // Debug: 检查所有SELs;日志方法列表内容
    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
}
复制代码

prepareMethodLists


...

    // 向数组中添加方法列表
    // 重新分配未固定的方法列表
    // 新方法被前置到方法列表数组中

    for (int i = 0; i < addedCount; i++) {
        method_list_t *mlist = addedLists[i];
        ASSERT(mlist);

        // 如有必要,修正选择器
        if (!mlist->isFixedUp()) {
            fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
        }
    }

...

复制代码

fixupMethodList

static void 
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
    runtimeLock.assertLocked();
    ASSERT(!mlist->isFixedUp());

    // fixme锁定更少的attachMethodLists ?
    // Dyld3可能已经对列表进行了惟一化,但没有排序
    if (!mlist->isUniqued()) {
        mutex_locker_t lock(selLock);
    
        // 列表中唯一的选择器
        for (auto& meth : *mlist) {
            const char *name = sel_cname(meth.name());
            // 拿到SEL 将名字 和 地址 设置 到 meth 中
            meth.setName(sel_registerNameNoLock(name, bundleCopy));
        }
    }

    // 按选择器地址排序
    // 不要试图对小列表进行排序,因为它们是不可变的
    // 不要试图对非标准大小的大列表进行排序,如stable_sort
    // 不能正确复制条目
    if (sort && !mlist->isSmallList() && mlist->entsize() == method_t::bigSize) {
        method_t::SortBySELAddress sorter;
        std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter);
    }
    
    // 将方法列表标记为惟一的和已排序的
    // 不能标记小列表,因为它们是不可变的
    if (!mlist->isSmallList()) {
        mlist->setFixedUp();
    }
}
复制代码

按照地址排序这一部分我们可以通过加代码答应来验证对于我们的SMPerson类中方法的排序前后对比

image.png

懒加载

实现了 +load 方法的类就是非懒加载类。 我们将SMPerson类中的+load方法注释掉, 然后在 realizeClassWithoutSwift 方法中 添加 上之前的 测试代码

const char *mangledName = cls->nonlazyMangledName();
    if (0 == strcmp(mangledName, "SMPerson")) {
        printf("\nSM realizeClassWithoutSwift -- %s - %s", __func__ , mangledName);
    }
复制代码

断点打好,接着运行项目后,常规命令 bt 查看下堆栈信息 :

image.png

我们发现此时的流程已经走到了 main() 函数之后, 此刻才是 SMPerson 类的数据处理。也就是说,懒加载类和非懒加载类的加载顺序是不同的,懒加载类将类信息的加载推迟到了第一次发消息的时候,也就是:

image.png

类的加载流程图

所以,类的加载流程是这样的:

类的加载.001.jpeg

关于分类

我们有一个SMPerson 名为 SM 的分类:

image.png

clang -rewrite-objc main.m -o main-cate.cpp 之后可以找到我们的分类,是一个 _category_t 类型的结构体:

static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
	&_OBJC_$_CATEGORY_SMPerson_$_SM,
};
复制代码

_category_t

// 分类没有元类
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;
};
复制代码

SMPerson (SM)


static struct _category_t _OBJC_$_CATEGORY_SMPerson_$_SM __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{   // 静态编译时,并没有跑起runtime,故,先贴过来
	"SMPerson",
	0, // &OBJC_CLASS_$_SMPerson,
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_SMPerson_$_SM,
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_SMPerson_$_SM,
	(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_SMPerson_$_SM,
	(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_SMPerson_$_SM,
};
static void OBJC_CATEGORY_SETUP_$_SMPerson_$_SM(void ) {
	_OBJC_$_CATEGORY_SMPerson_$_SM.cls = &OBJC_CLASS_$_SMPerson;
}
复制代码

_method_list_t

static struct /*_method_list_t*/ {
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[2];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_SMPerson_$_SM __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	2,
	{{(struct objc_selector *)"cate_instanceMethod2342134", "v16@0:8", (void *)_I_SMPerson_SM_cate_instanceMethod2342134},
	{(struct objc_selector *)"cate_instanceMethod6758675", "v16@0:8", (void *)_I_SMPerson_SM_cate_instanceMethod6758675}}
};

static struct /*_method_list_t*/ {
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_SMPerson_$_SM __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	1,
	{{(struct objc_selector *)"cate_classMethod2462457821", "v16@0:8", (void *)_C_SMPerson_SM_cate_classMethod2462457821}}
};

复制代码

那么,分类的数据是如何加载处理的呢?cpp文件我们看到分类的信息是存储在_category_t结构体中的,为什么我们可以直接通过SMPerson直接调用的呢? 这是我们接下来探索的重点。 在 上面 methodizeClass 方法中,有对于 rwe 的赋值操作

auto rwe = rw->ext();
复制代码

那么,就来到了 ext():

    class_rw_ext_t *ext() const {
        return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);
    }

    // 有 rwe 的时候 是可以获取得到的 否则 就 extAlloc 开辟
    class_rw_ext_t *extAllocIfNeeded() {
        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 {
            return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
        }
    }
复制代码

extAllocIfNeeded 的调取通过阅读源码发现是在一下情况下有调用: attachCategoriesobjc_class::demangledNameclass_setVersionaddMethods_finishclass_addProtocol_class_addPropertyobjc_duplicateClass ,那么,写一篇我们一起探索下关于类的加载部分关于分类加载的内容。大家,加油!!

补充

运行时环境变量

添加方法: Project -> Scheme -> Edit Scheme ,在 Arguments 下可以添加运行时的环境变量( Environment Variables )

查看列表:打开终端执行以下命令可查看完整列表

$ export OBJC_HELP=1
复制代码
文章分类
iOS
文章标签