面试好文

load和initialize的区别

2021-03-17  本文已影响0人  磊Se

调用机制

load方法的本质:直接执行函数指针

load方法是在运行时被执行的(main函数之前),其调用栈如下

_ load_images  
//加载类和类别的load方法
└── load_images_nolock 
    //执行所有load方法
    └── call_load_methods

而在load_images_nolock方法中,则调用了prepare_load_methods,其执行了两个方法:

_ prepare_load_methods  
//先将需要执行 load 的 class 添加到一个全局列表里 (loadable_class)
└── schedule_class_load 
    //然后将需要执行 load 的 category 添加到另一个全局列表里(loadable_category)
    └── add_category_to_loadable_list 

而在shedule_class_load方法中,确保先将父类添加到列表中。

static void schedule_class_load(class_t *cls)
{
    assert(isRealized(cls));  // _read_images should realize
    if (cls->data->flags & RW_LOADED) return;
    //确保先将父类添加到全局列表里 (loadable_class)
    class_t *supercls = getSuperclass(cls);
    if (supercls) schedule_class_load(supercls);
    //再将当前类添加到全局列表里 (loadable_class)
    add_class_to_loadable_list((Class)cls);
    changeInfo(cls, RW_LOADED, 0); 
}

然后再执行call_load_methods方法时

_ call_load_methods  
//先遍历 loadable_classes 列表中的类,执行 load  方法。
└── call_class_loads 
    //然后再遍历 loadable_category 列表中的分类 ,执行 load  方法。
    └── call_category_loads 

而在call_class_loads中,执行load方法的代码为

// Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Class cls = classes[i].cls;
        IMP load_method = classes[i].method;
        if (!cls) continue; 
        if (PrintLoading) {
            _objc_inform("LOAD: +[%s load]\n", _class_getName(cls));
        }
        (*load_method) ((id) cls, SEL_load);
    }

由以上可知,load方法,其实就是直接执行函数指针,不会执行消息发送objc_msgSend那一套流程。子类、分类的load方法不会覆盖父类的load方法。

initialize方法的本质

在类、或者子类,接收到第一条消息之前被执行(如初始化)
initialize方法最终通过objc_msgSend来执行
initialize方法在main函数之后调用
如果一直没有使用类,则initialize方法不会被调用
如果子类没有实现initialize方法,则会调用父类的initialize方法

源码分析:

__private_extern__ void _class_initialize(Class cls)
{
    Class supercls;
    BOOL reallyInitialize = NO;

    // Get the real class from the metaclass. The superclass chain 
    // hangs off the real class only.
    cls = _class_getNonMetaClass(cls);

    // Make sure super is done initializing BEFORE beginning to initialize cls.
    // See note about deadlock above.
    supercls = _class_getSuperclass(cls);
    if (supercls  &&  !_class_isInitialized(supercls)) {
        _class_initialize(supercls);
    }
    
    // Try to atomically set CLS_INITIALIZING.
    monitor_enter(&classInitLock);
    if (!_class_isInitialized(cls) && !_class_isInitializing(cls)) {
        _class_setInitializing(cls);
        reallyInitialize = YES;
    }
    monitor_exit(&classInitLock);
    
    if (reallyInitialize) {
        // We successfully set the CLS_INITIALIZING bit. Initialize the class.
        
        // Record that we're initializing this class so we can message it.
        _setThisThreadIsInitializingClass(cls);
        
        // Send the +initialize message.
        // Note that +initialize is sent to the superclass (again) if 
        // this class doesn't implement +initialize. 2157218
        if (PrintInitializing) {
            _objc_inform("INITIALIZE: calling +[%s initialize]",
                         _class_getName(cls));
        }

        ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);

        if (PrintInitializing) {
            _objc_inform("INITIALIZE: finished +[%s initialize]",
                         _class_getName(cls));
        }        
        
        // Done initializing. 
        ......
}

优先执行父类的initialize方法;通过_class_getSupercass取出父类,递归调用父类的initialize方法;initialize方法最终通过objc_msgSend来执行的。

执行顺序

load

场景1 :子类、父类、分类都实现load方法,调用情况
答:SuperClass->SubClass->CategoryClass
场景2 :子类、父类、分类中子类不实现load方法,调用情况
答:SuperClass->CategoryClass
场景3 :子类、父类、分类1、分类2都实现load方法,调用情况
答:SuperClass->SubClass->Category1Class->Category2Class

initialize

场景1 :子类、父类都实现initialize方法,调用情况
答:SuperClass->SubClass
场景2 :子类、父类中子类不实现initialize方法,调用情况
答:SuperClass->SuperClass(子类未实现,则会调用父类的initialize,导致父类调用多次)
场景3:子类、父类、子类分类都实现initialize方法,调用情况
答:SuperClass->CategoryClass(category中initialize方法覆盖其本类)
场景4:子类、父类、父类分类1、父类分类2都实现initialize方法,调用情况
答:CategoryClass->SubClass(category中initialize方法根据Compile Sources排序执行最后一个)

使用场景

面试题

1、runtime中的交换方法在initialize中实现会有什么问题?
答:initialize方法可能被其分类中的initialize方法覆盖,导致无法调用。

上一篇 下一篇

猜你喜欢

热点阅读