load VS initialize

1,937 阅读3分钟

load相关问题

Category中有load方法吗?

有load方法。

调用时机

load方法在runtime加载类、分类的时候调用,调用方式是直接调用方法,而不是通过消息机制触发调用。

load 方法能继承吗?

load方法可以继承,但是一般情况下不会主动去调用load方法,都是让系统自动调用。

调用顺序

  1. 先调用类的load方法。

    按照编译先后顺序调用,先编译的先调用。

  2. 调用子类的load方法之前会先调用父类的load方法。

  3. 再调用分类的load方法。

    按照编译先后顺序调用,先编译的先调用。

源码

// objc-os.mm/_objc_init(void)
void _objc_init(void)
{
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

// objc-runtime-new.mm/load_images
void load_images(const char *path __unused, const struct mach_header *mh)
{
  prepare_load_methods((const headerType *)mh);
  call_load_methods();
}

// objc-runtime-new.mm / prepare_load_methods 
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++) {
       // 整理要调用load方法的类
        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);
    }
}

// objc-runtime-new.mm / schedule_class_load 
static void schedule_class_load(Class cls)
{
    if (!cls) return;
    assert(cls->isRealized());  // _read_images should realize
  
   //类是否已经被加载到loadable_classes
    if (cls->data()->flags & RW_LOADED) return;

    // 确保父类排在前面
    schedule_class_load(cls->superclass);

    add_class_to_loadable_list(cls);
    // 标志该类已经添加到了loadable_classes
    cls->setInfo(RW_LOADED);
}
 
// objc-loadmethod.mm / add_class_to_loadable_list
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_used].cls = cls;
    loadable_classes[loadable_classes_used].method = method;
    loadable_classes_used++;
}

//objc-loadmethod.mm / 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) {
           //调用类的load方法
            call_class_loads();
        }

        // 2. Call category +loads ONCE
        // 调用Category的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);

    objc_autoreleasePoolPop(pool);

    loading = NO;
}

//objc-loadmethod.mm / 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;
    loadable_classes_used = 0;
    
    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Class cls = classes[i].cls;
       // 取出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());
        }
       //调用load方法。
        (*load_method)(cls, SEL_load);
    }
    
    // Destroy the detached list.
    if (classes) free(classes);
}

initialize 相关问题

调用时机

initialize会在类第一次接受到消息的时候调用。与load方法不同的是initialize是通过消息机制调用的即通过objc_msgsend()调用。

调用顺序

根据调用顺序触发。

调用子类的initialize会先触发父类的initialize

// objc-runtime-new.mm / class_getInstanceMethod
Method class_getInstanceMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;
    lookUpImpOrNil(cls, sel, nil, 
                   NO/*initialize*/, NO/*cache*/, YES/*resolver*/);
    return _class_getMethod(cls, sel);
}

// objc-runtime-new.mm / lookUpImpOrNil
IMP lookUpImpOrNil(Class cls, SEL sel, id inst, 
                   bool initialize, bool cache, bool resolver)
{
    IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
    if (imp == _objc_msgForward_impcache) return nil;
    else return imp;
}

// objc-runtime-new.mm / lookUpImpOrForward
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    IMP imp = nil;
   
    runtimeLock.read();
 
    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlockRead();
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.read();
    }

    return imp;
}

//  objc-initialize.mm / _class_initialize
void _class_initialize(Class cls)
{
    assert(!cls->isMetaClass());
    supercls = cls->superclass;
   // 递归调用,先调用父类的 initialize
    if (supercls  &&  !supercls->isInitialized()) {
        _class_initialize(supercls);
    }
    callInitialize(cls);  
}

// objc-initialize.mm / callInitialize
void callInitialize(Class cls)
{
    // 通过 objc_msgSend 进行调用
    ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
    asm("");
}

总结

load、initialize方法的区别什么?

调用方式

  1. load是根据函数地址直接调用
  2. initialize是通过objc_msgSend调用

调用时刻

  1. load是runtime加载类、分类的时候调用(只会调用1次)
  2. initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)

load、initialize的调用顺序?

  1. load

    1. 先调用类的load
      1. 先编译的类,优先调用load
      2. 调用子类的load之前,会先调用父类的load
  2. 再调用分类的load

    1. 先编译的分类,优先调用load
  3. initialize

    1. 先初始化父类
    2. 再初始化子类(可能最终调用的是父类的initialize方法)