RunTime源码阅读(五)之category原理

2020-01-17  本文已影响0人  某非著名程序员

源码版本:objc-runtime-750

1.category源码入口

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[])
{
    static bool firstTime = YES;
    header_info *hList[mhCount];
    uint32_t hCount;
    size_t selrefCount = 0;

    // Perform first-time initialization if necessary.
    // This function is called before ordinary library initializers. 
    // fixme defer initialization until an objc-using image is found?
    if (firstTime) {
        preopt_init();
    }

    if (PrintImages) {
        _objc_inform("IMAGES: processing %u newly-mapped images...\n", mhCount);
    }


    // Find all images with Objective-C metadata.(查找所有带有Objective-C元数据的镜像。)
    hCount = 0;

    // Count classes. Size various table based on the total.
    int totalClasses = 0;
    int unoptimizedTotalClasses = 0;
    {
        ...
    if (hCount > 0) {
        /*
         hList:
         */
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);//入口
    }

    firstTime = NO;
}

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
    ...
    // Discover categories. :读取分类信息
    for (EACH_HEADER) {
        category_t **catlist = 
            _getObjc2CategoryList(hi, &count);
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();

        for (i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            Class cls = remapClass(cat->cls);//在realizeClass中已经有添加分类,这时候直接map查找

            if (!cls) {
                // Category's target class is missing (probably weak-linked).
                // Disavow any knowledge of this category.
                catlist[i] = nil;
                if (PrintConnecting) {
                    _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                 "missing weak-linked target class", 
                                 cat->name, cat);
                }
                continue;
            }

            // Process this category. 
            // First, register the category with its target class. 
            // Then, rebuild the class's method lists (etc) if 
            // the class is realized. 
            bool classExists = NO;
            if (cat->instanceMethods ||  cat->protocols  
                ||  cat->instanceProperties) 
            {
                addUnattachedCategoryForClass(cat, cls, hi);
                if (cls->isRealized()) {
                    remethodizeClass(cls);
                    classExists = YES;
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category -%s(%s) %s", 
                                 cls->nameForLogging(), cat->name, 
                                 classExists ? "on existing class" : "");
                }
            }

            if (cat->classMethods  ||  cat->protocols  
                ||  (hasClassProperties && cat->_classProperties)) 
            {
                addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                if (cls->ISA()->isRealized()) {
                    remethodizeClass(cls->ISA());
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category +%s(%s)", 
                                 cls->nameForLogging(), cat->name);
                }
            }
        }
    }
...
}

map_images_nolock方法:会解析Mach-o文件读取到header_info *hList
_read_images:从hList读取读取信息,包括:class、protocol、category、sel等。category是在最后加载的。

1.2原理

1.2.1 remethodizeClass

static void remethodizeClass(Class cls)
{
    category_list *cats;
    bool isMeta;
    runtimeLock.assertWriting();
    isMeta = cls->isMetaClass();
    // 获取cls中未完成整合的所有分类
    if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
        if (PrintConnecting) {
            _objc_inform("CLASS: attaching categories to class '%s' %s", 
                         cls->nameForLogging(), isMeta ? "(meta)" : "");
        }
        //将分类cats拼接到cls上
        attachCategories(cls, cats, true /*flush caches*/);        
        free(cats);
    }
}

static category_list *
unattachedCategoriesForClass(Class cls, bool realizing)
{
    runtimeLock.assertLocked();
    //疑问:NXMapRemove返回的是void *,可以直接强转成category_list?
    return (category_list *)NXMapRemove(unattachedCategories(), cls);//从当前未分配类别的NXMapTable移除cls
}

从为未加载类的分类中找到category_list,通过attachCategories拼接到原来类的方法列表中。

1.2.2 attachCategories

static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);
    bool isMeta = cls->isMetaClass();//是否是元类
    // 按照Category个数,分配对应的内存空间
    // 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;
    
    // 循环查找出Protocol list、Property list、Method list
    while (i--) {//倒序遍历,最先访问的最后编译的分类
        auto& entry = cats->list[i];//获取一个分类

        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);//获取该分类的方法列表
        if (mlist) {
            mlists[mcount++] = mlist;//最后编译的分类最先添加到分类数组中
            fromBundle |= entry.hi->isBundle();
        }

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

    auto rw = cls->data();//获取宿主类中的rw数据,其中包含宿主类的方法列表s信息
    // 主要是针对 分类中有关于内存管理相关方法情况下的 一些特殊处理
    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    /*
     rw代表类
     methods代表类的方法类别
     attachLists 将含有mcount个元素的mlists拼接到rw的methods上
     */
    rw->methods.attachLists(mlists, mcount);
    free(mlists);
    if (flush_caches  &&  mcount > 0) flushCaches(cls);

    rw->properties.attachLists(proplists, propcount);
    free(proplists);

    rw->protocols.attachLists(protolists, protocount);
    free(protolists);
}
  1. 按照Category个数,分配对应的数组mlists、proplists、protolists内存空间
  2. while循环把cats->list中的list赋给数组
  3. 把上面三个数组添加到对应的方法列表上

1.2.3 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;//重新设置元素总数
            //内存移动:把oldArray整体往后挪addedCount位置
            memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));
            //内存拷贝:把addedLists放到0-(addedCount-1)的位置
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            list = addedLists[0];
        } 
        else {
            // 1 list -> many lists
            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;
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
    }
  1. 根据新总数重新分配内存
  2. 重新设置元素总数
  3. 内存移动:把oldArray整体往后挪addedCount位置
  4. 内存拷贝:把addedLists放到0-(addedCount-1)的位置

结果导致分类的方法插在了类的方法列表前面,方法调用时是找到即停止。这也是为什么分类方法会优先与类的方法调用,真正意义上并不是覆盖。

总结:
1.分类添加的方法可以"覆盖"原类方法(效果是覆盖的,但原宿主类的方法仍然存在)
2.同名分类方法谁能生效取决于编译顺序(最后被编译的分类会优先生效)
3.名字相同的分类会引起编译报错。

上一篇 下一篇

猜你喜欢

热点阅读