iOS底层原理-类的加载(上)

277 阅读7分钟

前言

上一篇中我们了解了应用程序的加载流程,iOS通过dyld把相应的文件加载到内存中,然后调用main函数启动App,主要研究了在main函数之前那段时间的流程,而这篇我们再研究一下关于类的加载是如何加载到内存的?以及相关的rwro是在什么时候写入的?

源码分析

要分析类的加载,我们还是从源码入手,根据源码的执行流程慢慢去深入、分析各个阶段都做了什么?首先从App启动的_objc_init方法开始

_objc_init

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;

    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    runtime_init();
    exception_init();
#if __OBJC2__
    cache_t::init();
#endif
    _imp_implementationWithBlock_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

_objc_init函数主要做了以下事情:

  • 环境变量初始化
  • 关于线程key的绑定
  • 运行C++静态构造函数
  • Runtime运行时环境初始化
  • 初始化libobjc的异常处理系统
  • 缓存条件初始化
  • 启动回调机制
  • 镜像文件加载

下面重点分析一下_dyld_objc_notify_register方法,这里就是关于的加载流程。

_dyld_objc_notify_register

_dyld_objc_notify_register(&map_images, load_images, unmap_image);

上面的方法包含3个方法的调用,map_imagesload_imagesunmap_image,先来看一下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

**********************************************************************/
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调用了map_images_nolock方法,再看一下map_images_nolock的定义

void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    ...... // 省略部分代码
    
    if (hCount > 0) {
        // 读取镜像文件
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }
    ...... // 省略部分代码
}

map_images_nolock调用了_read_images,读取镜像文件,接下来再看_read_images函数的定义。

_read_images

通过读取_read_images方法,主要包含了以下10个流程:

  • 条件控制进行一次的加载
  • 修复预编译阶段@selector的混乱问题
  • 错误混乱的类处理
  • 修复重映射一些没有被镜像文件加载进来的类
  • 修复一些消息
  • 当类里面有协议的时候读取协议
  • 修复没有被加载的协议
  • 分类处理
  • 类的加载处理
  • 没有被处理的类,优化那些被侵犯的类
void _read_images(header_info **hList, uint32_t hCount, 
                    int totalClasses, int unoptimizedTotalClasses)
{
    ...... // 省略部分代码
    if (!doneOnce) {
        ......
        
        // 表的创建,开辟的总容积 = 类的大小 * 4/3,4/3:负载因子,之前在cache_t分析中是3/4的逆运算
        int namedClassesSize = 
            (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
        // 总表,所有类的表
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
        ts.log("IMAGE TIMES: first time tasks");
    }
    
    // Discover classes. Fix up unresolved future classes. Mark bundle classes.
    bool hasDyldRoots = dyld_shared_cache_some_image_overridden();

    for (EACH_HEADER) {
        ......

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

            ......
        }
    }
    
    ...... // 省略部分代码
}

通过_read_images上面的流程,我们最终是研究加载类的信息,从上面获取到类的列表,开始读取类的信息,再看一下readClass的函数定义

readClass

Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    const char *mangledName = cls->nonlazyMangledName();
    
    ......
    
    cls->fixupBackwardDeployingStableSwift();

    Class replacing = nil;
    if (mangledName != nullptr) {
        if (Class newCls = popFutureNamedClass(mangledName)) {
            ......

            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) {
        ......
    } 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();
            ......
        }
        addClassTableEntry(cls);
    }

    ......
    
    return cls;
}

readClass的代码中我们看到了读取ro,对rw赋值的相关操作,但实际是否按代码这样操作呢?接下来通过LLDB调试的方式来验证一下,首先需要在readClass中加上一些判断代码,要确认进来的类是我们研究的对象,这里以LGPerson为例。

const char *mangledName = cls->nonlazyMangledName();

const char *LGPersonName = "LGPerson";
if (strcmp(mangledName, LGPersonName) == 0) {
    // 普通写得类 定位分析打印
    printf("%s - Atom: 要研究的: - %s\n",__func__,mangledName);
}

mangledName下增加上面代码,然后打个断点开始调试。 001.png 根据上面的运行结果可以得知,rorw不在readClass这里实现,这里只做表的加入。因而readClass主要做了如下操作:

  • 读取class总表和mach-oclass表进行比对
  • 把类名和类的地址进行关联 从这里看还不是类操作rwro的流程,接着我们还是从LLDB调试的方式去跟踪相关的流程,把上面的研究对象代码继续放在上面_readImage方法的相关类加载的函数里,并在自定义打印代码处打上断点。
void _read_images(header_info **hList, uint32_t hCount, 
                    int totalClasses, int unoptimizedTotalClasses)
{
    // Realize non-lazy classes (for +load methods and static instances)
    for (EACH_HEADER) {
        classref_t const *classlist = hi->nlclslist(&count);
        for (i = 0; i < count; i++) {
            Class cls = remapClass(classlist[i]);
            if (!cls) continue;

            // ----------- 自定义类 开始 --------------
            const char *mangledName = cls->nonlazyMangledName();
            const char *LGPersonName = "LGPerson";
            // 节约内存 速度
            if (strcmp(mangledName, LGPersonName) == 0) {
                // 普通写得类 定位分析打印
                printf("%s Realize non-lazy classes -Atom: 要研究的: - %s\n",__func__,mangledName);
            }
            // ----------- 自定义类 结束 --------------

            addClassTableEntry(cls);

            if (cls->isSwiftStable()) {
                if (cls->swiftMetadataInitializer()) {
                    _objc_fatal("Swift class %s with a metadata initializer "
                                "is not allowed to be non-lazy",
                                cls->nameForLogging());
                }
                // fixme also disallow relocatable classes
                // We can't disallow all Swift classes because of
                // classes like Swift.__EmptyArrayStorage
            }
            realizeClassWithoutSwift(cls, nil);
        }
    }

    ts.log("IMAGE TIMES: realize non-lazy classes");

    // Realize newly-resolved future classes, in case CF manipulates them
    if (resolvedFutureClasses) {
        for (i = 0; i < resolvedFutureClassCount; i++) {
            Class cls = resolvedFutureClasses[i];

            // ----------- 自定义类 开始 --------------
            const char *mangledName = cls->nonlazyMangledName();
            const char *LGPersonName = "LGPerson";

            if (strcmp(mangledName, LGPersonName) == 0) {
                // 普通写得类 定位分析打印
                printf("%s -resolvedFutureClasses-Atom: 要研究的: - %s\n",__func__,mangledName);
            }
            // ----------- 自定义类 结束 --------------

            if (cls->isSwiftStable()) {
                _objc_fatal("Swift class is not allowed to be future");
            }
            realizeClassWithoutSwift(cls, nil);
            cls->setInstancesRequireRawIsaRecursively(false/*inherited*/);
        }
        free(resolvedFutureClasses);
    }
}

再次运行,发现当前运行并没有断住,难道类的加载过程不走这里?根据疑问我们发现上面的一段注释。

// Realize non-lazy classes (for +load methods and static instances)

非懒加载的类,也就是执行了+load方法会走这里,接着我们在自定义的LGPerson类里加上load方法,再次运行。 002.png 这次断点来到了我们判断条件里,一步步往下走,走到了realizeClassWithoutSwift函数,这就是我们接下来的研究重点。 003.png

realizeClassWithoutSwift

/***********************************************************************
* realizeClassWithoutSwift
* Performs first-time initialization on class cls, 
* including allocating its read-write data.
* Does not perform any Swift-side initialization.
* Returns the real class structure for the class. 
* Locking: runtimeLock must be write-locked by the caller
**********************************************************************/
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    class_rw_t *rw;
    Class supercls;
    Class metacls;
    
    ......

    // 从mach-o读取ro数据
    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 {
        // 这里从ro里复制一份到rw,class设置rw
        rw = objc::zalloc<class_rw_t>();
        rw->set_ro(ro);
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        cls->setData(rw);
    }

    ......

    supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

    // 设置类的继承关系和初始化ISA
    cls->setSuperclass(supercls);
    cls->initClassIsa(metacls);

    ......
    
    // Attach categories
    methodizeClass(cls, previously);

    return cls;
}

realizeClassWithoutSwift主要做了以下几点:

  • mach-o读取ro数据
  • ro的数据复制一份给rw
  • 设置class的继承链
  • 初始化ISA走位
  • 调用类的方法写入函数 根据realizeClassWithoutSwift的对rwro操作的代码,我们实际项目运行验证一下。 004.png 通过LLDB调试在这里可以打印出rorw信息,验证了在realizeClassWithoutSwift方法里对rorw的读取和赋值,在当前的函数的最下面我们可以看到调用了methodizeClass函数,接下来再看一下这个函数的源码。

methodizeClass

/***********************************************************************
* methodizeClass
* Fixes up cls's method list, protocol list, and property list.
* Attaches any outstanding categories.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
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();
    
    ......

    // Install methods and properties that the class implements itself.
    // 获取类的方法列表
    method_list_t *list = ro->baseMethods();
    if (list) {
        // 对方法名写入到methodLists,并进行排序
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
        if (rwe) rwe->methods.attachLists(&list, 1);
    }

    ......
}

methodizeClass主要包含以下几点:

  • 读取类的方法列表名
  • 对方法名写入到methodLists并进行排序
  • 获取rwe
  • 获取协议列表
  • 加载分类 上面methodizeClass调用了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];
        ASSERT(mlist);

        // Fixup selectors if necessary
        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 lock less in attachMethodLists ?
    // dyld3 may have already uniqued, but not sorted, the list
    if (!mlist->isUniqued()) {
        mutex_locker_t lock(selLock);
    
        // Unique selectors in list.
        for (auto& meth : *mlist) {
            const char *name = sel_cname(meth.name());
            meth.setName(sel_registerNameNoLock(name, bundleCopy));
        }
    }

    // Sort by selector address.
    // Don't try to sort small lists, as they're immutable.
    // Don't try to sort big lists of nonstandard size, as stable_sort
    // won't copy the entries properly.
    if (sort && !mlist->isSmallList() && mlist->entsize() == method_t::bigSize) {
        method_t::SortBySELAddress sorter;
        std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter);
    }
    
    // Mark method list as uniqued and sorted.
    // Can't mark small lists, since they're immutable.
    if (!mlist->isSmallList()) {
        mlist->setFixedUp();
    }
}

我们在fixupMethodList方法里插入一段打印的代码,然后运行看看自定义的类的信息。 005.png 这里看到在自定义的类LGPerson里经过fixupMethodList方法执行后,得到地址排好序的方法列表。

总结

根据上面的源码和运行结果分析,类的加载流程大致有下面几个:

  1. 通过调用readImages读取类的镜像文件
  2. readClass读取类的名字,并和类进行关联
  3. realizeClassWithoutSwift处理superclassisa,读取ro数据,并给rw赋值
  4. methodizeClass获取到ro里的methodlist,写入方法名并给方法列表排序

写到最后

在本篇中也提到了懒加载非懒加载rwe,以及在methodizeClass方法里对分类的加载,由于篇幅的原因,就放在后面的篇章中解析。