iO Category加载流程3

「这是我参与2022首次更文挑战的第19天,活动详情查看:2022首次更文挑战」。

再来看看attachLists这个方法是如何将list合二为一的。

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]));
        }
        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]));
        }
    }
复制代码

这里主要看memmove 和 memcpy这俩函数,memmove将原来的list往后移了addedCount,而memcpy则是将新的addedLists复制到了原来list的前面。这就说明了分类覆盖主类的同名方法并不是真正的覆盖,而是分类的方法被排到了前面,当方法查找时,则返回了分类的方法实现

调用被分类插队的同名方法

在分类和主类都添加sayYes方法,再在分类sayNo方法里输入以下代码

unsigned int methodCount = 0;
    Method *methodList = class_copyMethodList([self class], &methodCount);
    
    IMP lastIMP = nil;
    for (NSInteger i = 0; i < methodCount; ++i) {
        Method method = methodList[i];
        NSString *selName = NSStringFromSelector(method_getName(method));
        if ([@"sayYes" isEqualToString:selName]) {
            lastIMP = method_getImplementation(method);
            ((void(*)(void))lastIMP)();
        }
    }
复制代码

会发现主类和分类的sayYes都走了

image.png

顺便捋一捋+load方法的顺序

call_load_methods 之前,会调用prepare_load_methods去发现+load方法,而这里面会对非懒加载类和非懒加载分类做处理。

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);
        //printf("%s--%s\n",cls->nameForLogging(),cat->name);
        if (!cls) continue;  // category for ignored weak-linked class
        if (cls->isSwiftStable()) {
            _objc_fatal("Swift class extensions and categories on Swift "
                        "classes are not allowed to have +load methods");
        }
        realizeClassWithoutSwift(cls, nil);
        ASSERT(cls->ISA()->isRealized());
        add_category_to_loadable_list(cat);
    }
复制代码

进去schedule_class_load方法,里面有一段代码

// Ensure superclass-first ordering
    schedule_class_load(cls->superclass);

    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED);
复制代码

在将类的+load方法添加进类的加载列表的时候,会schedule_class_load先将父类的+load加进去,这样保证父类在子类之前调用+load,然后添加分类的+load方法到分类的加载列表,而分类的+load加载顺序则看文件编译的顺序了。

真正调用+load方法的是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
        while (loadable_classes_used > 0) {
            call_class_loads();
        }

        // 2. Call category +loads ONCE
        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;
}
复制代码

从代码里可以看出来,首先循环调用类的load方法,再调用分类的load方法。

验证一下

image.png

image.png

image.png

image.png

这里可能想到 分类里的+load为啥没有'覆盖' 类的+load方法打印呢,这里并没有做什么特别的处理,分类的+load确实'覆盖' 了类的+load方法前面了。
但是call_load_methods的时候,并没有走消息转发的机制(objc_msgSend),而是直接通过函数指针直接调用的((*load_method)(cls, @selector(load));),所以只要实现了就会调用。

总结以下load的调用顺序:父类先于子类调用,主类先于分类调用,分类则看文件编译顺序

分析就到这里,有误请大佬指正。

Guess you like

Origin juejin.im/post/7066379090256723976