OC底层之类的加载上篇

882 阅读22分钟

前文# OC底层之dyld重点分析了应用程序的加载流程,我们本文重点分析类的加载流程

应用程序的加载流程主要是使用dyld将磁盘上的Mach-O文件和虚拟内存表产生产生了联系,也就是确定了应用程序的虚拟内存,但是这个时候Mach-O文件并没有被加载到物理内存上,本文我们来研究Mach-O是怎么被加载到物理内存上的。

我们本文重点分析map_imagesload_images这两个函数。

经过一系列推导过程我们可以知道这两个函数在应用程序加载流程中的位置

  • 1、配置上下文环境和检查上下文环境
  • 2、加载共享缓库
  • 3、实例化主程序
  • 4、加载插入的动态库
  • 5、链接主程序
  • 6、链接插入的动态库
  • 7、递归绑定主程序以及主程序所依赖的库
    • 执行map_images函数(递归执行)
  • 8、递归绑定插入的镜像文件
  • 9、弱符号绑定
  • 10、执行所有的初始化操作
    • 执行load_images函数(递归执行)
  • 11、返回应用程序的main函数 具体推导过程见下文【推导过程】章节

核心函数分析

_objc_init

void _objc_init(void)
{
    // 通过这三行可知_objc_init只会被执行一次
    static bool initialized = false;
    if (initialized) return;
    initialized = true;

    // fixme defer initialization until an objc-using image is found?
    // 读取影响运行时的环境变量,如果需要还可一打印环境变量帮助
    environ_init();
    
    // 关于线程key的绑定,比如线程数据的析构函数
    tls_init();
    
    // 运行c++静态构造函数,在dyld调用我们的静态构造函数之前,
    // libc会调用_objc_init函数,所以我们需要手动初始化
    static_init();
    
    // 运行时环境初始化,里面主要是:
    // unattachedCategories
    // allocatedClasses
    runtime_init();
    
    // 异常处理系统初始化,实现逻辑是向底层系统注册一个回调函数,
    // 在底层系统报异常时调起这个函数,
    // 上层系统在这个函数中处理异常情况
    exception_init();
#if __OBJC2__
    // 缓存条件初始化 
    cache_t::init();
#endif

    // 启动回调机制,通常这不会做什么,因为所有初始化都是惰性的
    // 但是某些进程我们会迫不及待的加载trampolines dylib
    _imp_implementationWithBlock_init();
    
    // 向dyld注册函数
    // 分别为map_images、load_images和unmap_images
    // map_images、load_images这两个函数是我们要分析的重点
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

我们看一下runtime_init

void runtime_init(void)
{
    objc::unattachedCategories.init(32);
    objc::allocatedClasses.init();
}

初始化了两个表unattachedCategoriesallocatedClasses后面会用到

map_images

管理文件中和动态库中所有的符号(class protocol selector category)

/***********************************************************************
* map_images
* Process the given images which are being mapped in by dyld.
* 处理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_nolock
void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    // static保证这个方法只执行一次
    static bool firstTime = YES;
    header_info *hList[mhCount];
    uint32_t hCount;
    size_t selrefCount = 0;
    
    // 如果是第一次执行那么先去找共享缓存的入口位置在哪里
    if (firstTime) {
        preopt_init();
    }
    ......
    // Find all images with Objective-C metadata.
    // 找到所有镜像文件中类的数量
    hCount = 0;

    // Count classes. Size various table based on the total.
    int totalClasses = 0;
    int unoptimizedTotalClasses = 0;
    {
        uint32_t i = mhCount;
        // 遍历镜像文件读取header_info存储起来
        while (i--) {
            const headerType *mhdr = (const headerType *)mhdrs[i];
            auto hi = addHeader(mhdr, mhPaths[i], totalClasses, unoptimizedTotalClasses);
            ......
            // 我们在查看image list时包括本应用程序和动态库
            // 那么这里我推断就是我们的应用程序
            if (mhdr->filetype == MH_EXECUTE) {
                ......
                // 做一些优化操作
            }
            ......
            // 从镜像文件中获取header_info存储起来
            // 通过header_info就可以读取出来该镜像文件中的类
            hList[hCount++] = hi;
        }
    }
    ......
    // 上面我们读取了所有镜像文件的header_info
    // 这里我们根据header_info读取类
    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }

    firstTime = NO;

    // Call image load funcs after everything is set up.
    // 执行镜像文件中所有非懒加载类的load方法
    for (auto func : loadImageFuncs) {
        for (uint32_t i = 0; i < mhCount; i++) {
            func(mhdrs[i]);
        }
    }
}

这个函数遍历所有镜像文件,从镜像文件中读取header_info存储在一个数组中,然后通过_read_images读取所有的类

_read_images

通过前文分析我们知道这个函数是遍历所有的header_info然后从中读取出相关信息,这个函数的功能包括

  • 1、条件控制进行一次的加载
  • 2、修复预编译阶段的@selector的混乱问题
  • 3、错误混乱的类处理
  • 4、修复重映射一些没有被镜像文件加载进来的类
  • 5、修复一些消息
  • 6、当我们类里面有协议的时候:readProtocol
  • 7、修复没有被加载的协议
  • 8、分类的处理
  • 9、类的加载处理
  • 10、没有被处理的类 优化哪些被侵犯的类 我们要研究类的加载流程,所以这里只分析9、类的加载处理
/***********************************************************************
* _read_images
* Perform initial processing of the headers in the linked 
* list beginning with headerList. 
*
* Called by: map_images_nolock
*
* Locking: runtimeLock acquired by map_images
**********************************************************************/
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
    ......
    // Realize non-lazy classes (for +load methods and static instances)
    // 实现非懒加载类,就是实现了+load()函数的类和静态实例对象
    for (EACH_HEADER) {
        // 从header_info中获取nlclslist
        // 即non-lazy class list非懒加载类列表😄
        classref_t const *classlist = hi->nlclslist(&count);
        for (i = 0; i < count; i++) {
            Class cls = remapClass(classlist[i]);
            if (!cls) continue;
            // 找个地方把非懒加载类存起来
            addClassTableEntry(cls);
            ......
            // 实现这些非懒加载类
            realizeClassWithoutSwift(cls, nil);
        }
    }

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

这里的流程很简单

  • 1、遍历所有的header_info,从中读取出来所有的非懒加载类,找个地方存起来
  • 2、实现这些非懒加载类 我们看看这些非懒加载类是存储到哪里了
addClassTableEntry
/***********************************************************************
* addClassTableEntry
* Add a class to the table of all classes. If addMeta is true,
* automatically adds the metaclass of the class as well.
* Locking: runtimeLock must be held by the caller.
**********************************************************************/
static void
addClassTableEntry(Class cls, bool addMeta = true)
{
    runtimeLock.assertLocked();

    // This class is allowed to be a known class via the shared cache or via
    // data segments, but it is not allowed to be in the dynamic table already.
    auto &set = objc::allocatedClasses.get();

    ASSERT(set.find(cls) == set.end());

    if (!isKnownClass(cls))
        set.insert(cls);
        
    if (addMeta)
        addClassTableEntry(cls->ISA(), false);
}

存储在allocatedClasses表中,这个表不就是上文中runtime_init()中创建的吗😄,呼应上了!!!

  • 如果是并且第一次被加载就存储这个
  • 如果是元类就存储类的isa指针 再看一下非懒加载类的实现过程
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
* 类cls的第一次初始化,包括可读可写的结构rw,返回类的真正的结构
* 也就是把类从磁盘上加载到了物理内存上了
**********************************************************************/
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?
    // class_ro_t是编译器就确定的结构,此时cls的bits中存储的是
    // 指向class_ro_t结构的指针,类被加载到物理内存的时候生成真正的
    // 类结构,这个时候类的bits变为存储指向class_rw_t的指针
    auto ro = (const class_ro_t *)cls->data();
    
    // 该类是不是元类
    auto isMeta = ro->flags & RO_META;
    
    // future类我也不知道是个啥,可以认为是出现异常情况的类
    // 这里系统对这种类做一些特殊处理,
    // 这是系统的工作,我们不需要太关心
    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.
        // 这里是正常的类,我们主要关注这里
        // 开辟一个可以容纳class_rw_t大小的内存空间
        rw = objc::zalloc<class_rw_t>();
        // rw中存储指向ro的指针,修改rw的一些标志位
        rw->set_ro(ro);
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        // 类的bits中存储指向rw的指针
        cls->setData(rw);
        
        // 这里主要做了完善class和rw、ro的关联关系,我们在
        // 类的结构章节已经探索过
    }

    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
    // 根据备注得知,这是要给类整一个索引值,如果isa指针是
    // RawIsa时用到
    // RawIsa:就是一个纯粹的指针,没有存储引用计数等其他信息
    cls->chooseClassArrayIndex();
    ......

    // 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.
    // 递归实现类的父类和元类的父类
    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就是rawIsa,即纯粹的指针
        cls->setInstancesRequireRawIsa();
    } else {
        // Disable non-pointer isa for some classes and/or platforms.
        // Set instancesRequireRawIsa.
        // 特殊情况下有些类也是RawIsa
        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;
        }

        // 以上这些情况就是要求使用RawIsa的类,在类里面标记一下
        if (instancesRequireRawIsa) {
            cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
        }
    }
// SUPPORT_NONPOINTER_ISA
#endif

    // Update superclass and metaclass in case of remapping
    // 完善类的继承关系  superclass和isa指针
    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
    // 将一些标记位从ro拷贝到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;
}

这个函数围绕对类结构的操作,由于我们在前面分析过类结构,所以这里看起来还是相对比较容易的。

编译阶段产生了只读的class_ro_t结构,运行阶段产生了可读可写的class_rw_t结构,这一步完善了类对象class_rw_tclass_ro_t相互之间的关系。设置了class_rw_t中的一些标志位。优化或者调整了类中的一些结构,例如对成员变量的内存重排。

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.
    // 将方法列表、属性列表、协议列表等信息从ro拷贝到rwe中一份
    // rwe在wwdc2020中详细介绍过,为了减少dirty memory
    method_list_t *list = ro->baseMethods();
    if (list) {
        // 对方法列表做了一些操作,我们后面会分析 
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
        // 拷贝方法列表
        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);
    }

    // Root classes get bonus method implementations if they don't have 
    // them already. These apply before category replacements.
    if (cls->isRootMetaclass()) {
        // root metaclass
        // 给根元类添加initialize方法,
        // 该方法在对象第一次接收消息的时候执行
        addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
    }
    
    ......
    
    // 加载分类信息到类中
    objc::unattachedCategories.attachToClass(cls, cls,
                                             isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
                                             
    ......
}

unattachedCategories就是上文中runtime_init()中创建的另一个表,又呼应上了😄

prepareMethodLists
static void 
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
                   bool baseMethods, bool methodsFromBundle, const char *why)
{
    runtimeLock.assertLocked();

    if (addedCount == 0) return;
    
    ......
    // 遍历二维方法数组,对每一个数组进行fix up
    // 根据前文可知addedCount = 1
    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.
    // 根据函数sel排序
    // 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();
    }
}

简单总结一下map_images的流程

  • 遍历所有景象文件,获取镜像文件的header_info
  • 遍历这些header_info读取所有的非懒加载类
  • 实现这些非懒加载类
    • 在物理内存上建立classrwrorwe之间的联系
    • 完善类的继承链和isa指针指向关系
    • 完善父类的子类列表
    • 对类进行优化,例如调整实例变量顺序,对方法列表排序等
    • 加载分类对象
  • 调用这些非懒加载类的load方法

到这里我们分析完了非懒加载类的流程,下面来分析懒加载类的流程

load_images

void
load_images(const char *path __unused, const struct mach_header *mh)
{
    if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
        didInitialAttachCategories = true;
        // 加载所有的分类
        loadAllCategories();
    }

    // Return without taking locks if there are no +load methods here.
    // 没有发现load方法直接return
    if (!hasLoadMethods((const headerType *)mh)) return;

    recursive_mutex_locker_t lock(loadMethodLock);

    // Discover load methods
    // 走到这里说明发现load方法了
    {
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    // 执行这些load方法
    call_load_methods();
}
loadAllCategories
static void loadAllCategories() {
    mutex_locker_t lock(runtimeLock);

    // 遍历上文中提到的所有镜像文件的header_info
    for (auto *hi = FirstHeader; hi != NULL; hi = hi->getNext()) {
        // 加载所有的分类
        load_categories_nolock(hi);
    }
}
load_categories_nolock
static void load_categories_nolock(header_info *hi) {
    bool hasClassProperties = hi->info()->hasCategoryClassProperties();

    size_t count;
    // processCatlist是一个函数指针,下面进行调用
    auto processCatlist = [&](category_t * const *catlist) {
        // 一个类可以有多个分类,遍历这些分类
        for (unsigned i = 0; i < count; i++) {
            // 获取分类
            category_t *cat = catlist[i];
            // 获取分类对应的类
            Class cls = remapClass(cat->cls);
            
            // struct locstamped_category_t {
            //    category_t *cat;
            //    struct header_info *hi;
            // };
            // 创建locstamped_category_t类型变量赋初值cat和hi
            locstamped_category_t lc{cat, hi};
            ......
            // Process this category.
            if (cls->isStubClass()) {
                // Stub classes are never realized. Stub classes
                // 没有realized的类称之为stub类,这里不分析
            } else {
                // First, register the category with its target class.
                // Then, rebuild the class's method lists (etc) if
                // the class is realized.
                if (cat->instanceMethods ||  cat->protocols
                    ||  cat->instanceProperties)
                {
                    if (cls->isRealized()) {
                        // 如果类已经实现,
                        // 那么把分类信息合并到主类上
                        attachCategories(cls, &lc, 1, ATTACH_EXISTING);
                    } else {
                        // 如果主类还没有实现
                        // 先存储到unattachedCategories
                        objc::unattachedCategories.addForClass(lc, cls);
                    }
                }

                // 分类中有类方法
                if (cat->classMethods  ||  cat->protocols
                    ||  (hasClassProperties && cat->_classProperties))
                {
                    if (cls->ISA()->isRealized()) {
                        // 元类已经实现
                        // 将分类中的类方法等信息
                        // 合并到主类的元类中
                        attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS);
                    } else {
                        // 否则暂时存储到unattachedCategories
                        objc::unattachedCategories.addForClass(lc, cls->ISA());
                    }
                }
            }
        }
    };

    // 这里将count的地址传入到catlist用来记录分类数量
    processCatlist(hi->catlist(&count));
    processCatlist(hi->catlist2(&count));
}

这里加载所有分类

  • 如果主类已经实现了,就将分类信息合并到主类上来
  • 如果主类尚未实现,就先存储到unattachedCategories
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)
{
    /*
     * Only a few classes have more than 64 categories during launch.
     * This uses a little stack, and avoids malloc.
     * 只有少数类有超过64个分类
     * 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.
     * 这段的意思应该是搞一个栈,先编译的分类先入栈,后编译的后入栈
     * 出栈的分类调用attachLists函数向函数列表前面插入
     * 最终结果就是后编译的分类的方法在前面,先编译的在后面
     */
     
    // 设置一个阀值64
    constexpr uint32_t ATTACH_BUFSIZ = 64;
    // 存放method_list_t的数组,method_list_t本身是数组
    // 所以这里是二维数组
    method_list_t   *mlists[ATTACH_BUFSIZ];
    // 存放property_list_t的数组
    property_list_t *proplists[ATTACH_BUFSIZ];
    // 存放protocol_list_t的数组
    protocol_list_t *protolists[ATTACH_BUFSIZ];

    // 分类中method数量
    uint32_t mcount = 0;
    // 分类中property数量
    uint32_t propcount = 0;
    // 分类中protocol数量
    uint32_t protocount = 0;
    bool fromBundle = NO;
    // 是否是元类
    bool isMeta = (flags & ATTACH_METACLASS);
    // 获取类的rwe,没有的话就创建一个
    auto rwe = cls->data()->extAllocIfNeeded();

    // 根据传入参数分析
    // cats_list = &lc
    // cats_count = 1
    // 所以这个for循环只走一次
    for (uint32_t i = 0; i < cats_count; i++) {
        // entry为locstamped_category_t结构体类型
        // 可以从entry中获取到分类cat和hi(header_info类型)
        auto& entry = cats_list[i];

        // 获取到分类中的实例方法列表/类方法列表
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            // 如果mlists满了
            if (mcount == ATTACH_BUFSIZ) {
                // 先对mlists中的方法做排序等操作,上文分析过
                prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
                // 将mlists插入到rwe的方法列表中
                rwe->methods.attachLists(mlists, mcount);
                mcount = 0;
            }
            // 将分类的方法列表插入到mlists中
            // 插入顺序是从后面向前挨个插入
            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的方法列表中去
        rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
    }
    
    rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
    rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}

分类列表cats_list是根据编译顺序确定的,例如编译顺序为cat1 -> cat2 -> cat3,那么cats_list中元素为

[cat1, cat2,cat3]

遍历和插入顺序为

for (uint32_t i = 0; i < cats_count; i++) {
    ......
    //mcount初始值为0
    // ATTACH_BUFSIZ - ++mcount 为数组最后一个位置
    mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
    ......
}

那么插入之后的顺序就成了

[
    [cat3_method0, cat3_method1....],
    [cat2_method0, cat2_method1....],
    [cat1_method0, cat1_method1....]
]

后编译的分类方法排在了前面

attachLists
void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;
        // 当前方法列表为二维数组
        if (hasArray()) {
            // many lists -> many lists
            
            // oldCount 原来数组中元素个数
            uint32_t oldCount = array()->count;
            // newCount 插入完之后数组中的元素个数
            uint32_t newCount = oldCount + addedCount;
            // 开辟新的空间存放新的结构体
            // 注意 array_t 是一个结构体类型
            array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
            // 给结构体中的 count属性赋值
            newArray->count = newCount;
            array()->count = newCount;

            // 注意看这里设计的很精妙
            for (int i = oldCount - 1; i >= 0; i--)
                // 给newArray结构体中的lists数组赋值
                // 将老数组中的元素放在后面oldCount的位置
                // 我们后面分析
                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指针就直接指向传入二维数组的第一个元素
            // 也就是当前数组列表是一维数组
            list = addedLists[0];
            validate();
        } 
        
        // 原来数组列表是一维数组,传入的数组列表是二维数组
        else {
            // 1 list -> many lists
            
            // 如果原来数组列表为空那么oldCount=0
            // 如果原来数组列表存在那么oldCount=1
            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();
        }
    }

这两个函数很不好理解,一会儿一维数组一会儿又二维数组快搞蒙了。那么我们先来看一下存储函数列表的模版类list_array_tt的结构

template <typename Element, typename List, template<typename> class Ptr>
class list_array_tt {
    
    // 结构体array_t包含一个二维数组和一个存放数量的count
    struct array_t {
        uint32_t count;
        Ptr<List> lists[0];
        ......
    };
    ......
public:
    // 联合体中包含一个一维数组和一个arrayAndFlag
    union {
        Ptr<List> list;
        uintptr_t arrayAndFlag;
    };
    
    // 联合体中的arrayAndFlag的最后一位作为标记
    // 如果为 1 那么为true
    // 如果为 0 那么为false
    bool hasArray() const {
        return arrayAndFlag & 1;
    }

    // 前提是hasArray()为true,才会访问这个函数
    // arrayAndFlag最后一位清零就是array_t的指针
    array_t *array() const {
        return (array_t *)(arrayAndFlag & ~1);
    }

    // 给arrayAndFlag赋值
    void setArray(array_t *array) {
        arrayAndFlag = (uintptr_t)array | 1;
    }
}

泛型赋值操作

  • Element = method_t
  • List = method_list_t
class method_array_t : 
    public list_array_tt<method_t, method_list_t, method_list_t_authed_ptr>
{
    ......
}

到这里可以知道list_array_tt支持两种存储方式

  • 1、存储在联合体中以一维数组的形式存储在list
[method_t, method_t, method_t ...]
  • 2、存储在结构体array_t中的二维数组lists
[
    [method_t, method_t ...],
    [method_t, method_t ...],
    [method_t, method_t ...]
]

联合体中的arrayAndFlag作为入口,通过最低一位来标记指向谁,arrayAndFlag & ~1作为指针

  • 最低一位为1:指向结构体array_t,此时为二维数组
  • 最低一位为0:指向联合体中的list,此时为一维数组
  • list为空:当前没有存储任何方法 到这里再回头分析函数attachLists
void attachLists(...){
    if (hasArray()) {
        // 步骤一: 指向array_t(二维数组)
    }else if (!list  &&  addedCount == 1) {
        // 步骤二: 当前没有存储任何方法
    }else {
        // 步骤三: 当前指向联合体中的list(一维数组)
    }
  • 步骤一分析
// 原来二维数组的元素个数
uint32_t oldCount = array()->count;
// 新数组的元素个数 老数组个数+新插入的元素个数
uint32_t newCount = oldCount + addedCount;

将原来的元素插入到新数组的后面

for (int i = oldCount - 1; i >= 0; i--)
    newArray->lists[i + addedCount] = array()->lists[i];

假设oldCount = 7addedCount = 3,那么newCount = 10,那么newArray->lists数组长度就为10,下标为0-9

  • i = oldCount - 1 = 6
  • i + addedCount = 9刚好是新数组的最后一个位置
  • array()->lists[i]老数组的最后一个元素 for循环执行oldCount次,刚好将老数组元素放在新数组的最后
      [[1],[2],[3],[4],[5],[6],[7]]
[ , , ,[1],[2],[3],[4],[5],[6],[7]]
//老数组赋值到新数组的后面

遍历插入的元素,插入新数组的前面

for (unsigned i = 0; i < addedCount; i++)
    newArray->lists[i] = addedLists[i];
[[1],[2],[3]]
[[1],[1],[1] ......]
//插入元素赋值到新数组的前面
  • 步骤二分析:当前没有存储任何方法,而且插入的二维数组只有一个元素,那么就将内层的一维数组赋值给list
list = addedLists[0];
// 插入的值为
[[0, 1...]]

list = [0, 1...]

步骤三分析:原来是一个一维数组,插入的是二维数组

// 老数组(一维数组)
[old-0, old-1, old-2...]

// 插入的二维数组
[[...],[...],[...]]

// 新数组,将老的一维数组整体放入二维数组最后一个位置
[[...],[...],[...],[old-0, old-1, old-2...]]

结合attachCategories综合分析,假如一个类clsA有三个分类cat1、cat2、cat3,编译顺序为cat1->cat2->cat3,在主类和分类都有实例方法funA,那么在主类和分类都没有实现load方法的前提下结果应该为

[[cat3-funA],[cat2-funA],[cat1-funA],[cls-funA]]

如果实例对象调用funA时就会执行分类cat3中的方法

注意这里分析的前提是-主类和分类都没有实现load方法,在非懒加载的情况下这里可能是一个一维数组,但是排列顺序依然是

[cat3-funA, cat2-funA, cat1-funA, cls-funA]

这种情况我们在后文中会做分析

prepare_load_methods
void prepare_load_methods(const headerType *mhdr)
{
    size_t count, i;

    runtimeLock.assertLocked();
    
    // 获取非懒加载类列表
    classref_t const *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        schedule_class_load(remapClass(classlist[i]));
    }

    // 获取非懒加载分类列表
    category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
    for (i = 0; i < count; i++) {
        category_t *cat = categorylist[i];
        Class cls = remapClass(cat->cls);
        if (!cls) continue;  // category for ignored weak-linked class
        ......
        // 如果分类实现了load方法,那么主类即使没有
        //实现load方法也要被迫营业
        // 在这里对主类进行了初始化
        realizeClassWithoutSwift(cls, nil);
        ASSERT(cls->ISA()->isRealized());
        // 添加分类到一个表中去
        add_category_to_loadable_list(cat);
    }
}
extAlloc

class_rw_ext_t是苹果在wwdc20提出的一个新的改动,主要为了减少dirty memory的大小,其结构为

struct class_rw_ext_t {
    DECLARE_AUTHED_PTR_TEMPLATE(class_ro_t)
    class_ro_t_authed_ptr<const class_ro_t> ro;
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    char *demangledName;
    uint32_t version;
};

开辟方法为:

class_rw_ext_t *
class_rw_t::extAlloc(const class_ro_t *ro, bool deepCopy)
{
    runtimeLock.assertLocked();
    // 开辟空间
    auto rwe = objc::zalloc<class_rw_ext_t>();

    rwe->version = (ro->flags & RO_META) ? 7 : 0;

    // 从ro拷贝方法列表
    method_list_t *list = ro->baseMethods();
    if (list) {
        if (deepCopy) list = list->duplicate();
        rwe->methods.attachLists(&list, 1);
    }

    // See comments in objc_duplicateClass
    // property lists and protocol lists historically
    // have not been deep-copied
    //
    // This is probably wrong and ought to be fixed some day
    // 从ro拷贝属性列表
    property_list_t *proplist = ro->baseProperties;
    if (proplist) {
        rwe->properties.attachLists(&proplist, 1);
    }

    // 从ro拷贝协议列表
    protocol_list_t *protolist = ro->baseProtocols;
    if (protolist) {
        rwe->protocols.attachLists(&protolist, 1);
    }
    
    // 正确处理rwe、rw的关系
    set_ro_or_rwe(rwe, ro);
    return rwe;
}

我们知道类结构为

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
}
  • 在编译期bits -> ro
  • 运行期
    • 没有动态插入的方法、属性等bits -> rw -> ro
    • 有动态插入的方法、属性等bits -> rw -> rwe -> ro rwe出现以后rw中的成员减少了,节省了dirty memory,具体细节请看wwdc20
schedule_class_load
/***********************************************************************
* prepare_load_methods
* Schedule +load for classes in this image, any un-+load-ed 
* superclasses in other images, and any categories in this image.
**********************************************************************/
// Recursively schedule +load for cls and any un-+load-ed superclasses.
// cls must already be connected.
static void schedule_class_load(Class cls)
{
    if (!cls) return;
    ASSERT(cls->isRealized());  // _read_images should realize

    if (cls->data()->flags & RW_LOADED) return;

    // Ensure superclass-first ordering
    // 递归实现,确保父类load方法在子类之前调用
    schedule_class_load(cls->getSuperclass());

    // 添加到一个已加载的表中
    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED); 
}
call_load_methods
void call_load_methods(void)
{
    static bool loading = NO;
    bool more_categories;

    loadMethodLock.assertLocked();

    // Re-entrant calls do nothing; the outermost call will finish the job.
    if (loading) return;
    loading = YES;

    void *pool = objc_autoreleasePoolPush();

    do {
        // 1. Repeatedly call class +loads until there aren't any more
        // 执行主类load方法
        while (loadable_classes_used > 0) {
            call_class_loads();
        }

        // 2. Call category +loads ONCE
        // 执行分类load
        more_categories = call_category_loads();

        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);

    loading = NO;
}

类的加载流程很复杂,我们在这里只是过了一遍主流程,后面我们还会通过一篇文章验证分析到的流程,敬请期待!!!!!!

推导过程

我们通过在控制台通过bt命令查看调用栈(删除一些地址和修饰信息之后)

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 5.1
    * frame #0: libobjc.A.dylib`_objc_init at objc-os.mm:925:9
      frame #1: libdispatch.dylib`_os_object_init + 13
      frame #2: libdispatch.dylib`libdispatch_init + 285
      frame #3: libSystem.B.dylib`libSystem_initializer + 238
      frame #4: dyld`ImageLoaderMachO::doModInitFunctions(ImageLoader::LinkContext const&) + 535
      frame #5: dyld`ImageLoaderMachO::doInitialization(ImageLoader::LinkContext const&) + 40
      frame #6: dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 492
      frame #7: dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 343
      frame #8: dyld`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 191
      frame #9: dyld`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 82
      frame #10: dyld`dyld::initializeMainExecutable() + 129
      frame #11: dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 9098
      frame #12: dyld`dyldbootstrap::start(dyld3::MachOLoaded const*, int, char const**, dyld3::MachOLoaded const*, unsigned long*) + 450
      frame #13: dyld`_dyld_start + 37
(lldb)

因为调用栈是先进后出,那么执行顺序就是

  • 1、 _dyld_start
  • 2、 dyldbootstrap::start
  • 3、 _main
  • 4、 initializeMainExecutable
  • 5、 runInitializers
  • 6、 processInitializers
  • 7、 recursiveInitialization
  • 8、 doInitialization
  • 9、 doModInitFunctions
  • 10、libSystem_initializer
  • 11、libdispatch_init
  • 12、_os_object_init
  • 13、_objc_init 我们看到1-9步都是在dyld中执行的,经过10-12步最终执行了_objc_init,简单理解为doModInitFunctions间接触发了_objc_init的执行

我们看一下doModInitFunctions的调用顺序

uintptr_t _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide,  int argc, const char* argv[], const char* envp[], const char* apple[],  uintptr_t* startGlue) 
{
    // 10、执行所有的初始化操作 
    // run all initializers
    initializeMainExecutable();
}

void initializeMainExecutable()
{
    sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);
}

void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingList& timingInfo)
{
    ......
    processInitializers(context, thisThread, timingInfo, up);
    ......
}

void ImageLoader::processInitializers(const LinkContext& context, mach_port_t thisThread,
InitializerTimingList& timingInfo, ImageLoader::UninitedUpwards& images)
{
    ......
    for (uintptr_t i=0; i < images.count; ++i) {
        images.imagesAndPaths[i].first->recursiveInitialization(context, thisThread, images.imagesAndPaths[i].second, timingInfo, ups);
    }
    ......
}

void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
  InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
    ......
    // let objc know we are about to initialize this image
    // 让objc知道我们要初始化这个镜像了
    uint64_t t1 = mach_absolute_time();
    fState = dyld_image_state_dependents_initialized;
    oldState = fState;
    context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);

    // initialize this image
    // 初始化镜像
    bool hasInitializers = this->doInitialization(context);

    // let anyone know we finished initializing this image
    // 让大家都知道我初始化完成了这个镜像
    fState = dyld_image_state_initialized;
    oldState = fState;
    context.notifySingle(dyld_image_state_initialized, this, NULL);
    ......
}

bool ImageLoaderMachO::doInitialization(const LinkContext& context)
{
    ......
    doModInitFunctions(context);
    ......
}

void ImageLoaderMachO::doModInitFunctions(const LinkContext& context)
{
    // 间接触发了_objc_init
}

initializeMainExecutable->runInitializers->processInitializers->recursiveInitialization->doInitialization->doModInitFunctions

通过前文我们知道recursiveInitialization方法中的notifySingle值为_dyld_objc_notify_register中的第二个参数init

static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo) { 
    ...... 
    (*sNotifyObjCInit)(image->getRealPath(), image->machHeader()); 
    ...... 
}

void registerObjCNotifiers(_dyld_objc_notify_mapped
                            mapped, 
                            _dyld_objc_notify_init init,
                            _dyld_objc_notify_unmapped unmapped) 
{ 
    ......
    // record functions to call 
    sNotifyObjCInit = init; 
    ...... 
}

void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,
                            _dyld_objc_notify_init init,
                            _dyld_objc_notify_unmapped  unmapped) 
{
    dyld::registerObjCNotifiers(mapped, init, unmapped); 
}

我们在libobjc源码中查看_dyld_objc_notify_register

void _objc_init(void)
{
    ......
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    ......
}

第二个参数为load_images函数。 到这里我们可以知道dyld的第10执行所有的初始化操作触发了load_images函数。

同样的方法我们可以推导出_dyld_objc_notify_register的第一个参数&map_images的赋值过程

void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                _dyld_objc_notify_init      init,
                                _dyld_objc_notify_unmapped  unmapped)
{
   dyld::registerObjCNotifiers(mapped, init, unmapped);
}

void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
   ......
   sNotifyObjCMapped = mapped;
   ......
}

调用过程

static void notifyBatchPartial(dyld_image_states state, bool orLater, dyld_image_state_change_handler onlyHandler, bool preflightOnly, bool onlyObjCMappedNotification)
{
   ......
   (*sNotifyObjCMapped)(objcImageCount, paths, mhs);
   ......
}

static void notifyBatch(dyld_image_states state, bool preflightOnly)
{
   notifyBatchPartial(state, false, NULL, preflightOnly, false);
}


uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
int argc, const char* argv[], const char* envp[], const char* apple[], 
uintptr_t* startGlue)
{
   // 7、递归绑定主程序以及主程序所依赖的库
   ......
   gLinkContext.notifyBatch(dyld_image_state_bound, false);
   ......
}

从这里我们知道了

  • 1、配置上下文环境和检查上下文环境
  • 2、加载共享缓库
  • 3、实例化主程序
  • 4、加载插入的动态库
  • 5、链接主程序
  • 6、链接插入的动态库
  • 7、递归绑定主程序以及主程序所依赖的库
    • 执行map_images函数(递归执行)
  • 8、递归绑定插入的镜像文件
  • 9、弱符号绑定
  • 10、执行所有的初始化操作
    • 执行load_images函数(递归执行)
  • 11、返回应用程序的main函数

参考文章

# ios底层-类的加载上 核心方法分析