ios 常用知识点详解ios进阶

Objective - C Category(二)load 方法

2020-04-15  本文已影响0人  爱玩游戏的iOS菜鸟

(一)load方法

(1)调用顺序
  1. 先调用类的+load
  1. 再调用分类的+load
(2)如何查看源码证明调用顺序?

源码解读顺序:
(1) objc-os.mm文件

(2) objc-runtime-new.mm文件

(3)objc-loadmethod.mm文件

重点函数讲解:

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 
        // 加载元类对象load方法
        while (loadable_classes_used > 0) {
            call_class_loads();
        }

        // 2. Call category +loads ONCE
        //加载categoty的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;
}

其中有call_class_loadscall_category_loads,下面只列举call_class_loads

static void call_class_loads(void)
{
    int i;
    
    // Detach current loadable list.
    //这个classes为前面`schedule_class_load`方法通过"整理"出来的数组
    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_method)(cls, SEL_load);
    }
    
    // Destroy the detached list.
    //调用load方法
    if (classes) free(classes);
}
(3)load方法与自定义方法的区别

看到这里也大概有点疑惑,为什么分类的自定义方法会“掩盖”类的原方法,而load不会?

(4)不同的类load方法调用顺序

上面只搞清楚了类与分类之间的调用顺序,那么不同的类(可能有继承关系)调用顺序又是什么样的呢?
回到前面的方法load_images,在调用call_load_methods方法前,调用了一个load_images_nolock方法,里面有一个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++) {
        //定制规划一些
        schedule_class_load(remapClass(classlist[i]));
    }

    //分类调用顺序 类的load调用完毕再调用分类,先编译先调用
    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);
    }
}

我们再看其中的schedule_class_load方法

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
    //递归调用(优先寻找其父类的load方法)
    schedule_class_load(cls->superclass);
    
    //将该类添加到loadable_list中
    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED); 
}

这个loadable_list是在后面call_class_loads调用时用到的,即可说明:

现在我们也能回答前面的面试题了:

4. Category中有load方法吗?load方法是什么时候调用的?load 方法能继承吗?

答:有load方法;load方法是在runtime加载类、分类的时候调用一次;load方法可以继承的,但是load方法一般是系统在调用,一般不会主动调用

(二)initialize方法

(1)调用顺序
  1. 先调用父类的+initialize,再调用子类的+initialize
(1)如何查看源码证明调用顺序?

源码解读顺序:
(1) objc-msg-arm64.s文件

(2) objc-runtime-new.mm文件

(2)+initialize和+load的区别

类方法调用:(还是会调用class_getInstanceMethod)

Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    return class_getInstanceMethod(cls->getMeta(), sel);//将自身传进去
}
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    Class curClass;
    IMP imp = nil;
    Method meth;
    bool triedResolver = NO;

    runtimeLock.assertUnlocked();

    // Optimistic cache lookup
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }

    if (!cls->isRealized()) {
        rwlock_writer_t lock(runtimeLock);
        realizeClass(cls);
    }

    //判断是否需要初始化,且还没有被初始化过
    if (initialize  &&  !cls->isInitialized()) {
        //(第一次的时候才会执行)
        _class_initialize (_class_getNonMetaClass(cls, inst));
        // If sel == initialize, _class_initialize will send +initialize and 
        // then the messenger will send +initialize again after this 
        // procedure finishes. Of course, if this is not being called 
        // from the messenger then it won't happen. 2778172
    }

    ....//代码省略

    return imp;
}

我们再看_class_initialize方法:

void _class_initialize(Class cls)
{
    assert(!cls->isMetaClass());

    Class supercls;
    BOOL reallyInitialize = NO;

    //重点: initialize之前需要保证父类已initialized 
    // Make sure super is done initializing BEFORE beginning to initialize cls.
    // See note about deadlock above.
    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) {
        _class_initialize(supercls);//递归初始化
    }
    
    // Try to atomically set CLS_INITIALIZING.
    {
        monitor_locker_t lock(classInitLock);
        if (!cls->isInitialized() && !cls->isInitializing()) {
            cls->setInitializing();
            reallyInitialize = YES;
        }
    }
    
    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]",
                         cls->nameForLogging());
        }
        
        //重点:通过objc_msgSend方法调用initialize方法
        ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);

        if (PrintInitializing) {
            _objc_inform("INITIALIZE: finished +[%s initialize]",
                         cls->nameForLogging());
        }        
        
        // Done initializing. 
        // If the superclass is also done initializing, then update 
        //   the info bits and notify waiting threads.
        // If not, update them later. (This can happen if this +initialize 
        //   was itself triggered from inside a superclass +initialize.)
        monitor_locker_t lock(classInitLock);
        if (!supercls  ||  supercls->isInitialized()) {
            _finishInitializing(cls, supercls);
        } else {
            _finishInitializingAfter(cls, supercls);
        }
        return;
    }

   ...//代码省略
}

因此,通过该方法可以看出, 此处是会先调用父类的initalize,再调用子类的initalize

load和initialize方法的区别是什么?

  1. 调用方式

ps: 上一期的遗留,同时思考load与initalize方法调用顺序,即原理

上一篇下一篇

猜你喜欢

热点阅读