类的加载原理

1,096 阅读13分钟

objc-init分析

image.png 分别调用了很多方法分别是:

  • environ_init() : 读取影响运⾏时的环境变量。如果需要,还可以打印环境变量帮助。
  • tls_init():关于线程key的绑定 - ⽐如每线程数据的析构函数
  • static_init() :运⾏C ++静态构造函数。在dyld调⽤我们的静态构造函数之前,libc 会调⽤_objc_init(),因此我们必须⾃⼰做
  • lock_init(): 没有重写,采⽤C++的特性
  • exception_init() 初始化libobjc的异常处理系统
  • cache_init(): 缓存条件初始化
  • runtime_init() : runtime运⾏时环境初始化,⾥⾯主要是:unattachedCategories,allocatedClasses 后⾯会分析
  • _imp_implementationWithBlock_init :启动回调机制。通常这不会做什么,因为所有的初始化都 是惰性的,但是对于某些进程,我们会迫不及待地加载trampolines dylib。 了解即可。
  • 重点

_dyld_objc_notify_register(&map_images, load_images, unmap_image); 分析

  • map_images() dyldimage镜像文件加载到内存中会调用该函数。
  • load_images() dyld初始化所有的image镜像文件文件会调用。
  • unmap_image:将image镜像文件移除时会调用。

load_images方法其实就是调用load方法,map_image方法,&map_images是指针传递,指向是同一块实现的地址,如果有什么变化就可以第一时间知道。在dyldsNotifyObjCMapped调用的地方是在notifyBatchPartial方法中,而notifyBatchPartial方法是在registerObjCNotifiers中调用,在objc初始化注册通知时就调用了,所以是先调用map_images后调用load_images这么个函数执行顺序。

  • &map_images 地址值作为参数,即指针传递,数据同步进行变化。map_images 函数是一个不断递归的函数

map_images 函数分析

/***********************************************************************
* map_images
* Process the given images which are being mapped in by dyld.
* Calls ABI-agnostic code after taking ABI-specific locks.
*
* Locking: write-locks runtimeLock
 * 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
* 处理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
* 对链接中的头执行初始处理 
* 以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
}
  • 1、条件控制进行一次的加载
  • 2、修复预编译阶段‘@selector’的混乱问题
  • 3、错误混乱的类处理
  • 4、修复重映射一些没有被镜像文件加载进来的类
  • 5、修复一些消息
  • 6、当我们类里面有协议的时候:readProtocol
  • 7、修复没有被加载的协议
  • 8、分类处理
  • 9、类的加载处理
  • 10、没有被处理的类优化那些被侵犯的类

1.条件控制进行一次的加载 doneOnce

if (!doneOnce) {
    doneOnce = YES;
    launchTime = YES;
    ...
    ...
    // namedClasses
    // Preoptimized classes don't go in this table.
    // 4/3 is NXMapTable's load factor
    // objc::unattachedCategories.init(32);
    // objc::allocatedClasses.init(); //(生成一张被开辟alloc的表)
    
    int namedClassesSize =
        (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
    //创建了一个表(总表关于类名的表,不管是否有被实现)
    gdb_objc_realized_classes =
        NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);

    ts.log("IMAGE TIMES: first time tasks");
}
  • 加载一次下次就不会再次进入判断,第一次进来主要创建表 gbd_objc_realized_classes,这是一张关于所有类的不管是否有没有被实现都存放进入。是一张总表。
  • 主要作用是生成一张关于全部类名的总表,不管这个类是否有被实现 alloc init
  • allocatedClass.init() ,生成一张已经被开辟内存的表。

2.修复预编译阶段‘@selector’的混乱问题

    // Fix up @selector references
    // sel 名字 + 地址
    static size_t UnfixedSelectors;
    {
        mutex_locker_t lock(selLock);
        for (EACH_HEADER) {
            if (hi->hasPreoptimizedSelectors()) continue;

            bool isBundle = hi->isBundle();
            SEL *sels = _getObjc2SelectorRefs(hi, &count);
            UnfixedSelectors += count;
            for (i = 0; i < count; i++) {
                const char *name = sel_cname(sels[i]);
                SEL sel = sel_registerNameNoLock(name, isBundle);
                if (sels[i] != sel) {
                    sels[i] = sel;
                }
            }
        }
    }
  • 不同的类中可能存在相同的方法,但是方法名相当地址是不同的。
  • 如:两个方法名相同,但是地址不同,需要进行局部处理。类的地址重定向。

sels[i]_getObjc2SelectorRefs是从MachO里面获取的,MachO有相对位移地址和偏移地址,selsel_registerNameNoLockdyld里面获取,dyld是链接整个程序的,所以以dyld的为准。因为方法是存放在类中,每个类中的位置是不一样的,所以方法的地址也就不一样,那么就必须对那些混乱的方法进行修复处理。

3、错误混乱的类处理

    // Discover classes. Fix up unresolved future classes. Mark bundle classes.
    //发现类。修复未解决的未来类。马克包类。
    bool hasDyldRoots = dyld_shared_cache_some_image_overridden();

    for (EACH_HEADER) {
        if (! mustReadClasses(hi, hasDyldRoots)) {
            // Image is sufficiently optimized that we need not call readClass()
            continue;
        }

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

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

        for (i = 0; i < count; i++) {
            Class cls = (Class)classlist[i];
            Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);

            if (newCls != cls  &&  newCls) {
                // Class was moved but not deleted. Currently this occurs 
                // only when the new class resolved a future class.
                // Non-lazily realize the class below.
                resolvedFutureClasses = (Class *)
                    realloc(resolvedFutureClasses, 
                            (resolvedFutureClassCount+1) * sizeof(Class));
                resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
            }
        }
    }

    ts.log("IMAGE TIMES: discover classes");
  • 清除没有清理干净的类

image.png

  • 类的地址读取加载。
  • readClass方法的作用:把类名和地址关联起来。

readClass 类的读取 现在开始研究我们自己写的类是如何加载的

/***********************************************************************
* readClass
* Read a class and metaclass as written by a compiler.
* Returns the new class pointer. This could be: 
* - cls
* - nil  (cls has a missing weak-linked superclass)
* - something else (space for this class was reserved by a future class)
*
* Note that all work performed by this function is preflighted by 
* mustReadClasses(). Do not change this function without updating that one.
*
* Locking: runtimeLock acquired by map_images or objc_readClassPair
**********************************************************************/
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    //获取类名
    const char *mangledName = cls->nonlazyMangledName();
    
    const char *LGPersonName = "LGPerson";

    if (strcmp(mangledName, LGPersonName) == 0) {
        // 普通写得类 他是如何
        printf("%s -KC: 要研究的: - %s\n",__func__,mangledName);
    }
    
    if (missingWeakSuperclass(cls)) {
        // No superclass (probably weak-linked). 
        // Disavow any knowledge of this subclass.
        if (PrintConnecting) {
            _objc_inform("CLASS: IGNORING class '%s' with "
                         "missing weak-linked superclass", 
                         cls->nameForLogging());
        }
        addRemappedClass(cls, nil);
        cls->setSuperclass(nil);
        return nil;
    }
    
    cls->fixupBackwardDeployingStableSwift();

    Class replacing = nil;
    if (mangledName != nullptr) {
        if (Class newCls = popFutureNamedClass(mangledName)) {
            // This name was previously allocated as a future class.
            // Copy objc_class to future class's struct.
            // Preserve future's rw data block.

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

            // Manually set address-discriminated ptrauthed fields
            // so that newCls gets the correct signatures.
            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) {
        // class list built in shared cache
        // fixme strict assert doesn't work because of duplicates
        // 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);
    }

    // for future reference: shared cache never contains MH_BUNDLEs
    if (headerIsBundle) {
        cls->data()->flags |= RO_FROM_BUNDLE;
        cls->ISA()->data()->flags |= RO_FROM_BUNDLE;
    }
    
    return cls;
}

  • 1.在自己写的类创建之前断点。 image.png
  • 2.写上语句判断打印并断点
const char *LGPersonName = "LGPerson";

if (strcmp(mangledName, LGPersonName) == 0) {
    // 普通写得类 他是如何
    printf("%s -KC: 要研究的: - %s\n",__func__,mangledName);
}

image.png

  • 3.打印信息提示 image.png

加载类流程

  • 1.readClass -> 这里并没有对类的ro,rw的赋值 nonlazyMangleName 获取类型
  • 2.addNameClass(cls,magleName,replacing)-> 将类型和地址关联起来
  • 3.addClassTableEntry(cls) ,在这里加载类以及元类 inset(cls)addMeta
  • 这里主要完成的是将类加入到表中,将这个类名的类以及元类插入到另一张哈希表中。这张表中的类都是初始化过的类 image.png

realizeClassWithoutSwift(cls, nil); 的分析流程 重点分析

  • 主要作用是 处理了ro rw 父类 元类isa 的相关处理
/***********************************************************************
* realizeClassWithoutSwift
* 对类cls执行首次初始化
* 包括分配它的读写数据
* 没有执行任何swift端初始化
* 返回类的实际类结构 
* 锁定:runtimeLock必须被调用者写锁定
- 主要作用是 处理了ro rw 父类 元类isa 的相关处理
**********************************************************************/
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;
}
  • ro: 只读沙盒路径下的内存
  • rw: 脏内存ro的复制用于修改的内存
  • rwe: extation 拓展类

methodizeClass(cls, previously)

  • methodelizeClass() 相关方法的排序
/***********************************************************************
* 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
}
  • prepareMehodList 获取ro中的baseMethods方法列表。然后将方法名和地址相关联,再进行类排序。 ro中的baseMethods()方法 方法名写入+排序
...

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

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

image.png

懒加载与非懒加载类 :

  • 当前类是否实现load方法,load方法会让一个类从懒加载类成为一个非懒加载类,懒加载类的好处是能够按需分配内存,节约内存。
  • 懒加载类和非懒加载类的加载顺序是不同的,懒加载类将类信息的加载推迟到了第一次发消息的时候,也就是懒加载是按需加载的。
  • 1.懒加载类情况 : 数据加载推迟到第一次消息的时候
    • lookUpImpOrForward //慢速查找
    • realizeClassMaybeSwiftMaybeRelock
    • realizeClassWithoutSwift
    • methodizeClass
  • 2.非懒加载类情况 : map_images的时候加载了所有类数据
    • readClass
    • _getObjc2NonlazyClassList
    • realizeClassWithoutSwift
    • methodizeClass

分类的本质

#import <Foundation/Foundation.h>
#import "LGPerson.h"
#import <objc/runtime.h>

extern void _objc_autoreleasePoolPrint(void);

@interface LGPerson (LG)

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

@property (nonatomic, assign) int cate_age;

- (void)cate_instanceMethod1;
- (void)cate_instanceMethod2;
+ (void)cate_classMethod3;

@end

@implementation LGPerson (LG)

- (void)cate_instanceMethod1 {
    NSLog(@"%s",__func__);
}
- (void)cate_instanceMethod2 {
    NSLog(@"%s",__func__);
}
+ (void)cate_classMethod3 {
    NSLog(@"%s",__func__);
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        LGPerson *p = [LGPerson alloc];
        [p say1];
        
        NSLog(@"Hello, World!");
    }
    return 0;
}

__attribute__((constructor)) void kcFunc(void){
    printf("来了 : %s \n",__func__);
}


我们在 main.mLGPerson 类添加一个分类 LGPerson (LG)cdmain.m 所在文件目录,然后输入 clang -rewrite-objc main.m -o main.cpp 命令,最后会看到生成了一个 main.cpp 文件。

image.png

image.png

image.png

我们打开 main.cpp 文件,在最后可以看到 LGPerson (LG) 的底层代码。接着我们点进到 _category_t 查看分类的结构。我们可以看到结构体里面有 name,这里 name 的名称就是 LGcls 这里就是就是 LGPerson 类,而且可以看到 instance_methodsclass_methods,这里相对于类的结构多了 class_methods,这是因为分类没有元类,接着就是 protocols(协议)properties(关联属性)

image.png

这里我们也可以看到对应我们添加的类方法跟对象方法,但是这里没有 set 方法跟 get 方法,也验证了分类添加属性是通过关联对象进行处理的。这里是生成的 cpp 文件我们看到的,那么在源码中分类的结构是否跟这里一致呢?

image.png

这里可以看到结构跟我们在 cpp 文件中看到的基本一直,只是多了一个 _classProperties,但是类属性不是一直都有。

ro、rw、rwe

  • ro:app 在使用类时,是需要在磁盘中 app 的二进制文件中读取类的信息,二进制文件中的类存储了类的元类、父类、flags 和方法缓存,而类的额外信息(name、方法、协议和实例变量等)存储在 class_ro_t 中。class_ro_t 简称 roread only,将类从磁盘中读取到内存中就是对 ro 的赋值操作。由于 ro 是只读的,加载到内存后不会发生改变又称为 clean memory(干净内存)。

image.pngcls->data()强转class_ro_t得到 image.png

image.png

  • rwclass_rw_t 简称 rwread write,用于读写编写程序。 drity memory 在进程运行时发生更改的内存。类一经使用运行时就会分配一个额外的内存,那么这个内存变成了drity memory。但是在实际应用中,类的使用量只是 10%,这样就在 rw 中造成了内存浪费,所以苹果就把 rw 中方法、协议和实例变量等放到了class_rw_ext_t 中。

  • rweclass_rw_ext_t 简称 rweread write ext,用于运行时存储类的方法、协议和实例变量等信息。

rwe的加载 attachCategories

image.png image.png

搜索可以看到分别在 load_categories_nolock 跟 attachToClass 这两个地方调用了 attachCategories 函数。

image.png

接着我们搜索 attachToClass 发现有三个地方调用了这个函数,但是都是在 methodizeClass 函数里面调用的。我们看到代码逻辑走到 if (previously) {} 的前提是 previously 有值,而 previously 是函数参数,所以我们搜索 methodizeClass 函数。

image.png

attachCategories

  • rwe 有函数 attachLists 获得 image.png
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;

            array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));

            newArray->count = newCount;

            array()->count = newCount;
            
            for (int i = oldCount - 1; i >= 0; i--)

                newArray->lists[i + addedCount] = array()->lists[i];

            for (unsigned i = 0; i < addedCount; i++)

                newArray->lists[i] = addedLists[i];

            free(array());

            setArray(newArray);

            validate();

        }

        else if (!list  &&  addedCount == 1) {

            // 0 lists -> 1 list

            list = addedLists[0];

            validate();

        } 

        else {

            // 1 list -> many lists

            Ptr<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;

            for (unsigned i = 0; i < addedCount; i++)

                array()->lists[i] = addedLists[i];

            validate();

        }

    }