美文网首页iOS 开发 Objective-C
iOS 底层 day06 Category-02 load i

iOS 底层 day06 Category-02 load i

作者: 望穿秋水小作坊 | 来源:发表于2020-08-28 18:08 被阅读0次

一、本节要解决的问题

1. +load 在什么时候调用?
  • +load 方法会在 runtime 加载类、分类的时候调用
2. +load 会被多次调用吗?
  • 在非手动调用的情况下,每个类、分类的 +load 在程序运行过程中只会调用一次
3. +load 在没有(category)分类的情况下,调用顺序由什么决定?
  • 由代码的编译顺序决定,先编译的先调用(编译顺序从 Xcode 中查看)
  • 调用子类的 +load,会优先保证父类的 +load被优先调用
4. +load 在有(category)分类的情况下,调用顺序是怎么样的?
  • 优先把所有 class 的 +load 方法调用结束,才会调用分类的 +load
  • (category)分类先编译的分类先调用 +load
5. +load 在 runtime 运行时,是使用objc_msgSend调用的吗?
  • 不是
  • 由 runtime 运行时直接拿到 +load 的方法地址,然后利用指针函数直接调用 +load 方法
6. +initialize 在什么时候调用?
  • +initialize 会在分类 第一次收到消息时调用
7. +initialize 的调用顺序?如果有分类呢?
  • 先调用父类的 +initialize ,再调用子类的 +initialize
  • 如果子类没有实现 +initialize,会调用父类的 +initialize (所以父类可能会被多次调用)
  • 如果分类实现了 +initialize,就会覆盖类本身的 +initialize 调用
8. +initialize的调用 是objc_msgSend消息传递调用吗?
  • 是的

二、 load

1. 我们先从 load 官方说明中提取几个定义
  1. +load 方法会在 runtime 加载类、分类时调用

  2. 调用一个类的 load 方法之前,会优先调用其 父类的 load 方法

  3. 分类 load 的调用必须在类的 load 调用完毕之后

  4. load 方法只会被调用一次,并且调用不是经过 objc_msgSend消息传递机制,而是找到其方法地址,用指针函数直接调用

2. 带着这些定义,从 objc4-723 我们解读一下 load 相关源码:
  • objc-os.mm 中找到 _objc_init , 这是 objc 初始化入口
  • 接着找到 load_images ,这里是 images 是镜像的意思
  • 接着找到 call_load_methods,我们从这里开始分析,主要注意中文部分
/***********************************************************************
* call_load_methods
* 调用 load 方法
* Call all pending class and category +load methods.
* 调用 所有未处理的 class 和 Category 的 +load 方法
* Class +load methods are called superclass-first. 
* class 的 +load 方法会优先调用父类的 +load
* Category +load methods are not called until after the parent class's +load.
* Category 的 +load 方法需要等 class 的 +load 调用结束能被调用
**********************************************************************/
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
        // 1. 循环调用所有的 class 的 +loads 
        while (loadable_classes_used > 0) {
            call_class_loads();
        }

        // 2. Call category +loads ONCE
        // 2. 在调用 category 的 +loads
        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;
}
  • 接着找到 call_load_methods
static void call_class_loads(void)
{
    int i;
    
    // Detach current loadable list.
    // 获取当前需要 loadable 的 list
    struct loadable_class *classes = loadable_classes;
    int used = loadable_classes_used;
    loadable_classes = nil;
    loadable_classes_allocated = 0;
    loadable_classes_used = 0;
    
    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Class cls = classes[i].cls;
        // 拿到 class的 +load 方法的地址
        load_method_t load_method = (load_method_t)classes[i].method;
        if (!cls) continue; 

        if (PrintLoading) {
            _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
        }
        // 利用指针调用 cls 的 +load 方法
        (*load_method)(cls, SEL_load);
    }
    
    // Destroy the detached list.
    if (classes) free(classes);
}
  • 现在存在一个疑惑, loadable_classes 这个class的数组,是怎么来的呢?内部是什么结构?这决定了 class的调用顺序,接下来我们看 prepare_load_methods 、schedule_class_load 、 add_class_to_loadable_list
void prepare_load_methods(const headerType *mhdr)
{
    size_t count, i;

    runtimeLock.assertWriting();

    classref_t *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        schedule_class_load(remapClass(classlist[i]));
    }

    category_t **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
        realizeClass(cls);
        assert(cls->ISA()->isRealized());
        add_category_to_loadable_list(cat);
    }
}

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
    // 确保所有父类优先加入到 loadable_classes 中,这里利用 schedule_class_load 进行递归优先取父类
    schedule_class_load(cls->superclass);
    
    // 将class加到 loadable_classes 中
    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED); 
}

void add_class_to_loadable_list(Class cls)
{
    IMP method;

    loadMethodLock.assertLocked();

    method = cls->getLoadMethod();
    if (!method) return;  // Don't bother if cls has no +load method
    
    if (PrintLoading) {
        _objc_inform("LOAD: class '%s' scheduled for +load", 
                     cls->nameForLogging());
    }
    
    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));
    }
    // 这里我们找到了 loadable_classes ,就是我刚刚用的,在这里进行父类优先的复制
    loadable_classes[loadable_classes_used].cls = cls;
    loadable_classes[loadable_classes_used].method = method;
    loadable_classes_used++;
}
  • class的+load的调用结束
  • 接着找到 call_category_loads,我们开始分析 Category加载load的情况
static bool call_category_loads(void)
{
    int i, shift;
    bool new_categories_added = NO;
    
    // Detach current loadable list.
       // 获取当前categories需要 loadable 的 list
    struct loadable_category *cats = loadable_categories;
    int used = loadable_categories_used;
    int allocated = loadable_categories_allocated;
    loadable_categories = nil;
    loadable_categories_allocated = 0;
    loadable_categories_used = 0;

    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Category cat = cats[i].cat;
       // 拿到 Category 的 +load 方法的地址
        load_method_t load_method = (load_method_t)cats[i].method;
        Class cls;
        if (!cat) continue;

        cls = _category_getClass(cat);
        if (cls  &&  cls->isLoadable()) {
            if (PrintLoading) {
                _objc_inform("LOAD: +[%s(%s) load]\n", 
                             cls->nameForLogging(), 
                             _category_getName(cat));
            }
           // 利用指针调用 cls 的 +load 方法
            (*load_method)(cls, SEL_load);
            cats[i].cat = nil;
        }
    }
}
  • 然后,我们分析 loadable_categories 这个数据的来源,主要从 prepare_load_methods 、add_category_to_loadable_list
void prepare_load_methods(const headerType *mhdr)
{
    size_t count, i;

    runtimeLock.assertWriting();
    
    // 获取objc中非懒加载的class
    classref_t *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        // 按照某种定制方法读取load class
        schedule_class_load(remapClass(classlist[i]));
    }
      // 读取需要load的CategoryList
    category_t **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
        realizeClass(cls);
        assert(cls->ISA()->isRealized());
        add_category_to_loadable_list(cat);
    }
}

void add_category_to_loadable_list(Category cat)
{
    IMP method;

    loadMethodLock.assertLocked();

    method = _category_getLoadMethod(cat);

    // Don't bother if cat has no +load method
    if (!method) return;

    if (PrintLoading) {
        _objc_inform("LOAD: category '%s(%s)' scheduled for +load", 
                     _category_getClassName(cat), _category_getName(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));
    }
    // 直接放入到 loadable_categories  数组中
    loadable_categories[loadable_categories_used].cat = cat;
    loadable_categories[loadable_categories_used].method = method;
    loadable_categories_used++;
}

  • 至此,我们摸清了 Category中的 load方法调用的来龙去脉

三、 initialize

1. initialize的几个特点
  • +initialize方法会在类第一次受到消息时调用
  • 调用顺序:先调用父类的+initialize,再调用子类的+initialize
  • +initialize是通过 objc_msgSend进行调用的,所以有以下特点
  • 如果子类没有实现+initialize,会调用父类的+initialize(所以父类的可能会被调用多次)
  • 如果分类实现了+initialize,就会覆盖类本身的+initialize调用
2. initialize的源码求证
  • 先找到 class_getInstanceMethod
  • 再找到 lookUpImpOrNil
  • 再找到 lookUpImpOrForward
  • 再找到 _class_initialize , 截取部分代码如下
/***********************************************************************
* class_initialize.  Send the '+initialize' message on demand to any
* uninitialized class. Force initialization of superclasses first.
*
**********************************************************************/
void _class_initialize(Class cls)
{
    assert(!cls->isMetaClass());

    Class supercls;
    bool reallyInitialize = NO;

    // Make sure super is done initializing BEFORE beginning to initialize cls.
    // 确保super父类的 +initialize 已经被调用过了,才能调用 cls,这里也是用_class_initialized递归实现的
    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) {
        _class_initialize(supercls);
    }
    
    callInitialize(cls);
}
  • 再找到 callInitialize , 代码如下
void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
    asm("");
}
  • 我们发现,+initialize 的方法调用,本质就是 objc_msgSend的调用,也就是消息发送

相关文章

网友评论

    本文标题:iOS 底层 day06 Category-02 load i

    本文链接:https://www.haomeiwen.com/subject/ggqhsktx.html