iOS DeveloperiOS程序猿程序员

OC底层探索18 - 类的加载(下)

2021-06-24  本文已影响0人  Henry________

在上一篇OC底层探索17 - 类的加载(上)中对类的名称、data、方法、属性、协议的注入完成了分析。还留下了一个问题就是类中分类的加载

本文调试源码objc4-818.2,所以结论也仅限于该版本。

二、 分类的加载

书接上文,在methodizeClass中发现了attachToClass这个方法中对分类方法进行了处理。

前提:

@implementation HRTest
@property(nonatomic, copy)NSString *name;
-(void)sayHappy;
+(void)load{ }
@end

@implementation HRTest (cate1)
-(void)sayHappy;
-(void)sayHappyCate1;
+(void)load{ }
@end

1、分类的加载时机

static void methodizeClass(Class cls, Class previously)
{
    ...
    objc::unattachedCategories.attachToClass(cls, cls,
                                             isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
}
void attachToClass(Class cls, Class previously, int flags)
{
    auto &map = get();
    auto it = map.find(previously);
    if (it != map.end()) {
        category_list &list = it->second;
        if (flags & ATTACH_CLASS_AND_METACLASS) {
            int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS;
            attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS);
            //类别中类方法添加到元类中去
            attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS);
        } else {
            attachCategories(cls, list.array(), list.count(), flags);
        }
        map.erase(it);
    }
}

所有分类的加载是在map_images之后的load_images里被调起的,真的是这样吗?记得在文章的开始有提过一个前提,是类、分类中都实现了+load方法,如果没有实现这个方法呢?

1.1 类、分类都不实现+load

我们知道如果类中不实现load方法,则该类是一个懒加载类,类的加载时机推迟到第一次消息调用。那个分类的加载时机是什么时候呢?

  1. 断点设置在methodizeClass,因为attachCategories不会被调用;
  2. 堆栈信息看到起点是在类第一次消息发送时;
  3. 在类从mach-o中读出ro时,类、分类的方法都已经保存在ro里了;

1.2 类、分类的4类情况

分类 类加载情况 分类加载情况
load load 类在map_image加载 分类在load_image加载
load 类在map_image加载 分类方法已经通过mach-o读取到ro里
load 类被标记为非懒加载类,在map_image加载 分类方法已经通过mach-o读取到ro里
类在第一次消息转发时加载 分类方法已经通过mach-o读取到ro里

2、分类的加载

只有在类、分类都实现+load方法才会执行,其他情况都是直接从mach-o中读取出来的。

static void load_categories_nolock(header_info *hi) {
    size_t count;
    //所有分类进行循环
    auto processCatlist = [&](category_t * const *catlist) {
        for (unsigned i = 0; i < count; i++) {
            if (cls->isRealized()) {
                attachCategories(cls, &lc, 1, ATTACH_EXISTING);
            }
        }
    ...
}

static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count...) {
    // 脏内存是wwdc2020提出的一种类的内存优化
    // extAllocIfNeeded 调用该方法之后才生成rwe。
    // 分类、addmethod、addprotocol、addproperty四种情况下才会产生rwe脏内存
    auto rwe = cls->data()->extAllocIfNeeded();
    
    //根据调试cats_count = 1,该循环只执行一次,该类的其他分类是通过
    for (uint32_t i = 0; i < cats_count; i++) {
        auto& entry = cats_list[i];
        //拿出分类中的方法
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            //把分类放到最后一位64号位置,猜测方便查询
            mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
            fromBundle |= entry.hi->isBundle();
        }
        // 属性、协议的方法注入就省略了
        ....
    }
    if (mcount > 0) {
        //依旧进行排序
        prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
                           NO, fromBundle, __func__);
        //插入方法列表
        rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
    }
}

3、分类方法的插入

OC底层探索17 - 类的加载(上)已经提到过该方法的一种情况,事实上该方法有3种情况.

void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (hasArray()) {
            // many lists -> many lists
            uint32_t oldCount = array()->count;
            uint32_t newCount = oldCount + addedCount;
            //数组进行扩容
            array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
            newArray->count = newCount;
            array()->count = newCount;
            //旧数组元素从后往前插
            for (int i = oldCount - 1; i >= 0; i--)
                newArray->lists[i + addedCount] = array()->lists[i];
            //新数组元素从前往后插
            for (unsigned i = 0; i < addedCount; i++)
                newArray->lists[i] = addedLists[i];
            free(array());
            setArray(newArray);
        }
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            list = addedLists[0];
        } 
        else {
            // 1 list -> many lists
            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;
            // 把旧数组当做一个元素放到lists最后一位
            if (oldList) array()->lists[addedCount] = oldList;
            // 把新数组从头依次放入
            for (unsigned i = 0; i < addedCount; i++)
                array()->lists[i] = addedLists[i];
        }
    }

三、 load_images(非懒加载类)

map_images完成后,还记得在_objc_init - _dyld_objc_notify_register(&map_images, load_images, unmap_image);中的load_images吗?

void
load_images(const char *path __unused, const struct mach_header *mh)
{
    if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
        didInitialAttachCategories = true;
        //非懒加载类的分类中实现load方法后,通过该方法完成分类的加载.
        loadAllCategories();
    }
    // Discover load methods
    {
        //准备所有load方法
        prepare_load_methods((const headerType *)mh);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    //调用所有load方法
    call_load_methods();
}

1、prepare_load_methods 准备所有load方法

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

    classref_t const *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        schedule_class_load(remapClass(classlist[i]));
    }
    //获取所有实现load方法的分类
    
    category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
    //所有分类的load方法都会被加载
    for (i = 0; i < count; i++) {
        category_t *cat = categorylist[i];
        Class cls = remapClass(cat->cls);
        //非懒加载分类迫使主类完成加载
        realizeClassWithoutSwift(cls, nil);
        add_category_to_loadable_list(cat);
    }
}

1.1 add_class_to_loadable_list(类)

static void schedule_class_load(Class cls)
{
    // Ensure superclass-first ordering
    schedule_class_load(cls->getSuperclass());
}

void add_class_to_loadable_list(Class cls)
{
    IMP method;
    method = cls->getLoadMethod();
    //在数组到达上限时,完成数组的扩容
    if (loadable_classes_used == loadable_classes_allocated) {
        loadable_classes_allocated = loadable_classes_allocated*2 + 16;
        loadable_classes = (struct loadable_class *)
            //数组的扩容
            realloc(loadable_classes,
                              loadable_classes_allocated *
                              sizeof(struct loadable_class));
    }
    //将load方法和对应的类放入loadable_classes
    loadable_classes[loadable_classes_used].cls = cls;
    loadable_classes[loadable_classes_used].method = method;
    loadable_classes_used++;
}

1.2 add_category_to_loadable_list(分类)

void add_category_to_loadable_list(Category cat)
{
    IMP method;
    method = _category_getLoadMethod(cat);
    //扩容
    if (loadable_categories_used == loadable_categories_allocated) {
        loadable_categories_allocated = loadable_categories_allocated*2 + 16;
        loadable_categories = (struct loadable_category *)
            realloc(loadable_categories,
                              loadable_categories_allocated *
                              sizeof(struct loadable_category));
    }
    //将分类load方法加入loadable_categories
    loadable_categories[loadable_categories_used].cat = cat;
    loadable_categories[loadable_categories_used].method = method;
    loadable_categories_used++;
}

2、prepare_load_methods 调用所有load方法

void call_load_methods(void)
{
    bool more_categories;
    //当前循环值执行一次
    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);
}

2.1 call_class_loads

static void call_class_loads(void)
{
    int i;
    // Detach current loadable list.
    struct loadable_class *classes = loadable_classes;
    int used = loadable_classes_used;
    loadable_classes = nil;
    loadable_classes_allocated = 0;
    //直接设置为0,外层就不会继续循环
    loadable_classes_used = 0;
    
    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Class cls = classes[i].cls;
        load_method_t load_method = (load_method_t)classes[i].method;
        if (!cls) continue; 
        //通过函数指针调用
        (*load_method)(cls, @selector(load));
    }
    //释放class
    if (classes) free(classes);
}

面试题

题:如果类和分类有同名方法,调用会调用哪个方法?
答:两种情况:

  1. 如果是普通方法,则会调用分类中的重名方法
  2. 如果是+load方法,则先调用类中的+load,在依次调用分类的load.

总结

类的加载-分类的加载-load方法调用后,加载一个类所有的工作都已经完成了,等待后续使用。

上一篇下一篇

猜你喜欢

热点阅读