系统底层源码分析(8)——Category(分类)加载流程

上篇了解了分类的编译与运行和一些知识后就接着深入来看看底层源码。

Category加载会由dyld开始进行:

_objc_initObject-C runtime 的入口函数,在这里面主要功能是读取 Mach-O 文件 OC 对应的 Segment seciton ,并根据其中的数据代码信息,完成为 OC 的内存布局,以及初始化 runtime 相关的数据结构。

  1. objc4-750源码探究,我们从_objc_init入手:
void _objc_init(void)
{
    ...
    //分别对应:dyid将image加载进内存,dyid初始化image,移除image出内存
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);//image指的不是图片而是mach-o二进制文件
}
  1. 然后我们重点看map_images
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);
}
void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    ...
    if (hCount > 0) {
        //读取oc相关的Section, 来进行初始化
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);//这里的images是镜像的意思
    }

    firstTime = NO;
}
  1. ( 注意:cls=class;cats=Categories
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
    ...
    for (EACH_HEADER) {
        category_t **catlist = 
            _getObjc2CategoryList(hi, &count);//读取分类列表
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();

        for (i = 0; i < count; i++) {
            ...
            if (cat->instanceMethods ||  cat->protocols  
                ||  cat->instanceProperties) 
            {
                addUnattachedCategoryForClass(cat, cls, hi);//把分类添加到类里
                if (cls->isRealized()) {
                    remethodizeClass(cls);//把类进行重排
                    classExists = YES;
                }
                ...
            }
            ...
        }
    }
    ...
}

3-1. 会先读取分类列表:

3-2. 接着把分类添加到类里:

static void addUnattachedCategoryForClass(category_t *cat, Class cls, 
                                          header_info *catHeader)
{
    runtimeLock.assertLocked();

    // DO NOT use cat->cls! cls may be cat->cls->isa instead
    NXMapTable *cats = unattachedCategories();// // 取得存储所有未依附分类的列表:cats
    category_list *list;

    list = (category_list *)NXMapGet(cats, cls);// 从 cats 列表中找到 cls 对应的未依附分类的列表:list
    ...
    NXMapInsert(cats, cls, list);//把cats和cls建立关联映射
}

3-3. 然后把类进行重排:

static void remethodizeClass(Class cls)
{
    ...
    // Re-methodizing: check for more categories
    if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
        if (PrintConnecting) { ... }
        //添加分类方法、属性、协议
        attachCategories(cls, cats, true /*flush caches*/); 
        free(cats);
    }
}
  1. 终于到了添加分类方法、属性、协议:
static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);

    bool isMeta = cls->isMetaClass();

    // fixme rearrange to remove these intermediate allocations
    //二维数组;声明内存大小
    method_list_t **mlists = (method_list_t **)
        malloc(cats->count * sizeof(*mlists));//方法列表
    property_list_t **proplists = (property_list_t **)
        malloc(cats->count * sizeof(*proplists));//属性列表
    protocol_list_t **protolists = (protocol_list_t **)
        malloc(cats->count * sizeof(*protolists));//协议列表

    // Count backwards through cats to get newest categories first
    int mcount = 0;// 记录方法的数量
    int propcount = 0;// 记录属性的数量
    int protocount = 0;// 记录协议的数量
    int i = cats->count;// 从分类数组最后开始遍历,保证先取的是最新的分类
    bool fromBundle = NO;// 记录是否是从 bundle 中取的
    while (i--) {// 从后往前依次遍历
        auto& entry = cats->list[i];// 取出当前分类

        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;// 将方法列表放入 mlists 方法列表数组中
            fromBundle |= entry.hi->isBundle();// 分类的头部信息中存储了是否是 bundle,将其记住
        }
        // 取出分类中的属性列表,如果是元类,取得的是 nil
        property_list_t *proplist = 
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            proplists[propcount++] = proplist;
        }
        // 取出分类中的协议列表
        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist;
        }
    }
    // 取出当前类 cls 的 class_rw_t 数据
    auto rw = cls->data();
    
    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);//准备方法列表
    rw->methods.attachLists(mlists, mcount);//添加分类方法到类方法数组;重点逻辑
    free(mlists);// 释放
    if (flush_caches  &&  mcount > 0) flushCaches(cls);// 清除 cls 的缓存列表
    // 将新属性列表添加到 rw 中的属性列表中
    rw->properties.attachLists(proplists, propcount);
    free(proplists);
    // 将新协议列表添加到 rw 中的协议列表中
    rw->protocols.attachLists(protolists, protocount);
    free(protolists);
}

4-1. 先看看methods,准备方法列表:

static void 
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount, 
                   bool baseMethods, bool methodsFromBundle)
{
    ...
    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*/);//修正方法列表
        }
        ...
}
static void 
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
    runtimeLock.assertLocked();
    assert(!mlist->isFixedUp());

    // fixme lock less in attachMethodLists ?
    {
        mutex_locker_t lock(selLock);
    
        // Unique selectors in list.
        for (auto& meth : *mlist) {
            const char *name = sel_cname(meth.name);
            meth.name = sel_registerNameNoLock(name, bundleCopy);
        }
    }

    // Sort by selector address.    排序
    if (sort) {
        method_t::SortBySELAddress sorter;
        std::stable_sort(mlist->begin(), mlist->end(), sorter);
    }
    
    // Mark method list as uniqued and sorted
    mlist->setFixedUp();
}

4-2. 最终方法、属性、协议都进行attachLists

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;//新的大小
        setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
        array()->count = newCount;

        memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));//在内存中往后移动,腾出空间进行添加
        memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));//在内存中复制列表到前面刚腾出的空间里 ,所以后编译先调用,会先调用分类方法
    }
}
  • 所以分类和类的同名方法,分类的方法会先调用。

你可能感兴趣的:(系统底层源码分析(8)——Category(分类)加载流程)