阅读 80

MJiOS底层笔记--load && initialize

本文属笔记性质,主要针对自己理解不太透彻的地方进行记录。

推荐系统直接学习小码哥iOS底层原理班---MJ老师的课确实不错,强推一波。


干货

load

+load方法会在runtime加载类、分类时调用

每个类、分类的+load,在程序运行过程中只调用一次

调用顺序

先调用类的+load

按照编译先后顺序调用(先编译,先调用)

调用子类的+load之前会先调用父类的+load

再调用分类的+load

按照编译先后顺序调用(先编译,先调用)

initialize

+initialize方法会在类第一次接收到消息时调用

调用顺序

先调用父类的+initialize,再调用子类的+initialize

(先初始化父类,再初始化子类,每个类只会初始化1次)

二者区别

+initialize+load的很大区别是,+initialize是通过objc_msgSend进行调用的,所以有以下特点

  1. 如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次)

  2. 如果分类实现了+initialize,就覆盖类本身的+initialize调用


load

程序加载时,调用所有挂起的类和类别+load方法

先调用类的+load,再调用分类的+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 {
        / **** 首先调用class的load方法 ***/
        while (loadable_classes_used > 0) {
            call_class_loads();
        }

        / **** 首先调用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;
}
复制代码

调用的具体逻辑

子类的+load之前会先调用父类的+load,先编译,先调用 通过内存地址(IMP)直接调用

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_method_t load_method = (load_method_t)classes[i].method;
        if (!cls) continue; 

        if (PrintLoading) {
            _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
        }
        
        // 直接通过IMP函数指针调用
        // IMP(Class,SEL),load(cls,SEL)
        (*load_method)(cls, SEL_load);
    }
    
    // Destroy the detached list.
    if (classes) free(classes);
}
复制代码

父类load先于子类调用

程序加载,去构建load列表

void load_images(const char *path __unused, const struct mach_header *mh)
{
    // Return without taking locks if there are no +load methods here.
    if (!hasLoadMethods((const headerType *)mh)) return;

    recursive_mutex_locker_t lock(loadMethodLock);

    // Discover load methods
    {
        rwlock_writer_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();
}


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

    runtimeLock.assertWriting();
    
    // _getObjc2NonlazyClassList的顺序与编译顺序相同
    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);
    }
}
复制代码

构建load方法列表时,先添加父类load再添加子类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;

    //递归调用,先调用父类
    schedule_class_load(cls->superclass);

    //将类追加到列表末尾
    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED); 
}
复制代码

为什么load方法系统调用时不会被分类覆盖

直接提取出IMP调用,并未经过消息转发 但如果[Person load],只能调用到分类方法


initialize

向一个类第一次主动发送消息时调用。

在进行方法查找时

当一个类的方法第一次被调用,会进入查找逻辑。并加入cache,第二次直接在cache读取。

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

    // This deliberately avoids +initialize because it historically did so.

    // This implementation is a bit weird because it's the only place that 
    // wants a Method instead of an IMP.

#warning fixme build and search caches
        
    // Search method lists, try method resolver, etc.
    lookUpImpOrNil(cls, sel, nil, 
                   NO/*initialize*/, NO/*cache*/, YES/*resolver*/);

#warning fixme build and search caches

    return _class_getMethod(cls, sel);
}
复制代码

查找IMP指针时尝试初始化

/**
 在一个类对象中查找某个SEL的IMP实现
 */
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    ...
    
    //如果需要初始化,并且未被初始化
    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlockRead();
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.read();
    }

    ...
    return imp;
}
复制代码

初始化一个类

优先初始化父类,然后初始化自己

void _class_initialize(Class cls)
{
    ...
    ...
    
    //优先初始化父类
    if (supercls  &&  !supercls->isInitialized()) {
        _class_initialize(supercls);
    }

    
    ...
    ...
    
    
    if (reallyInitialize) {
        ...
        
        
        @try
#endif
        {
            callInitialize(cls);

            if (PrintInitializing) {
                _objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
                             pthread_self(), cls->nameForLogging());
            }
        }
#if __OBJC2__
        @catch (...) {
            if (PrintInitializing) {
                _objc_inform("INITIALIZE: thread %p: +[%s initialize] "
                             "threw an exception",
                             pthread_self(), cls->nameForLogging());
            }
            @throw;
        }
        @finally
#endif
        {
            // Done initializing.
            lockAndFinishInitializing(cls, supercls);
        }
        return;
    }
    
    else if (cls->isInitializing()) {
        // We couldn't set INITIALIZING because INITIALIZING was already set.
        // If this thread set it earlier, continue normally.
        // If some other thread set it, block until initialize is done.
        // It's ok if INITIALIZING changes to INITIALIZED while we're here, 
        //   because we safely check for INITIALIZED inside the lock 
        //   before blocking.
        if (_thisThreadIsInitializingClass(cls)) {
            return;
        } else if (!MultithreadedForkChild) {
            waitForInitializeToComplete(cls);
            return;
        } else {
            // We're on the child side of fork(), facing a class that
            // was initializing by some other thread when fork() was called.
            _setThisThreadIsInitializingClass(cls);
            performForkChildInitialize(cls, supercls);
        }
    }
    
    else if (cls->isInitialized()) {
        // Set CLS_INITIALIZING failed because someone else already 
        //   initialized the class. Continue normally.
        // NOTE this check must come AFTER the ISINITIALIZING case.
        // Otherwise: Another thread is initializing this class. ISINITIALIZED 
        //   is false. Skip this clause. Then the other thread finishes 
        //   initialization and sets INITIALIZING=no and INITIALIZED=yes. 
        //   Skip the ISINITIALIZING clause. Die horribly.
        return;
    }
    
    else {
        // We shouldn't be here. 
        _objc_fatal("thread-safe class init in objc runtime is buggy!");
    }
}

复制代码

对自身进行初始化

void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
    asm("");
}

复制代码

如果子类没有实现,父类会被多次调用

Initialize的调用属于消息转发,如果cls本身没有实现,则会去父类查找并调用。

void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
    asm("");
}
复制代码
关注下面的标签,发现更多相似文章
评论