美文网首页编写高质量代码的52个有效方法
52个有效方法(51) - 精简initialize与load的

52个有效方法(51) - 精简initialize与load的

作者: SkyMing一C | 来源:发表于2018-10-11 15:53 被阅读26次

    +load

    • 对于加入运行期系统的类及分类,必定会调用此方法,且仅调用一次。

    • iOS会在应用程序启动的时候调用+load方法,在main函数之前调用。

    • 执行子类的+load方法前,会先执行所有超类的load方法,顺序为父类->子类->分类。

    • +load方法中使用其他类是不安全的,因为会调用其他类的+load方法,而如果关系复杂的话,就无法判断出各个类的载入顺序,类只有初始化完成后,类实例才能进行正常使用。

    • +load 方法不遵从继承规则,如果类本身没有实现+load方法,那么系统就不会调用,不管父类有没有实现(跟下文的+initialize有明显区别)。

    • 尽可能的精简+load方法,因为整个应用程序在执行+load方法时会阻塞,即,程序会阻塞直到所有类的+load方法执行完毕,才会继续。

    • +load方法中最常用的就是方法交换method swizzling

    源码浅析
    +load方法的调用顺序图
    +load
    extern bool hasLoadMethods(const headerType *mhdr);
    extern void prepare_load_methods(const headerType *mhdr);
     
    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();
    }
    
    • 在runtime源码中,+load方法是在load_images中通过call_load_methods调用的。

    • 在运行时加载镜像时,通过prepare_load_methods方法将+load方法准备就绪,而后执行call_load_methods,调用+load方法。

    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]));
        }
        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
        schedule_class_load(cls->superclass);
     
        add_class_to_loadable_list(cls);
        cls->setInfo(RW_LOADED); 
    }
    
    • 在prepare_load_methods方法中,获取了所有类后,遍历列表,将其中有+load方法的类加入loadable_class

    • 在prepare_load_methods方法中,获取所有的类别后,遍历列表,将其中有+load方法的类加入loadable_categories

    • schedule_class_load方法中会首先通过schedule_class_load(cls->superclass)确保父类中的 +load方法被加入loadable_class(如果父类有+load方法的话),从而保证父类的+load方法总是在子类之前调用。也因此,在覆写+load方法时,不需要调用super方法。

    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) {
                call_class_loads();
            }
     
            // 2. Call category +loads ONCE
            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方法首先调用类的load方法。

    • call_class_loads方法中通过在第一步读取prepare_load_methods步骤里的loadable_classes,遍历列表并调用+load方法。

    • 然后类似的调用类别的+load方法。

    • 最后处理可能出现的异常。

    call_class_loads
    static void call_class_loads(void)
    {
        int i;
     
        //1.获取列表
        struct loadable_class *classes = loadable_classes;
        int used = loadable_classes_used;
        loadable_classes = nil;
        loadable_classes_allocated = 0;
        loadable_classes_used = 0;
     
        //2.循环调用load方法
        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; 
     
            (*load_method)(cls, SEL_load);
        }
     
        // 3. 释放列表
        if (classes) free(classes);
    }
    
    • +load方法的调用是通过直接使用函数内存地址的方式实现的,而不是更常见的objc_msgSend来发送消息。

    • 类,父类与分类之间+load方法的调用是互不影响的。

    • 子类不会主动调用父类的+load方法,如果类与分类都实现了+load',那么两个+load`方法都会被调用。

    +initialize

    • 在首次使用该类之前由运行期系统(非人为)调用,且仅调用一次。

    • 惰性调用,只有当程序使用相关类时,才会调用。

    • 运行期系统会确保+initialize方法是在线程安全的环境中执行,即,只有执行+initialize的那个线程可以操作类或类实例。其他线程都要先阻塞,等待initialize执行完。

    • 如果类未实现+initialize方法,而其超类实现了,那么会运行超类的实现代码,而且会运行两次(load 第5点)。

    • +initialize 遵循继承规则。

    • 初始化子类的的时候会先初始化父类,然后会调用父类的+initialize方法,而子类没有覆写+initialize方法,因此会再次调用父类的实现方法。

    • +initialize方法也需要尽量精简,一般只应该用来设置内部数据,比如,某个全局状态无法在编译期初始化,可以放在initialize里面。

    源码浅析
    +initialize流程图
    + (void)initialize {
    }
    
    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;
     
        supercls = cls->superclass;
        if (supercls  &&  !supercls->isInitialized()) {
            _class_initialize(supercls);
        }
     
        {
            monitor_locker_t lock(classInitLock);
            if (!cls->isInitialized() && !cls->isInitializing()) {
                cls->setInitializing();
                reallyInitialize = YES;
            }
        }
     
        if (reallyInitialize) {        
            _setThisThreadIsInitializingClass(cls);
     
            @try {
                callInitialize(cls);
            }
            @catch (...) {
                if (PrintInitializing) {
                    _objc_inform("INITIALIZE: +[%s initialize] threw an exception",
                                 cls->nameForLogging());
                }
                @throw;
            }
            @finally {
                if (!supercls  ||  supercls->isInitialized()) {
                    _finishInitializing(cls, supercls);
                } else {
                    _finishInitializingAfter(cls, supercls);
                }
            }
            return;
        }
        else if (cls->isInitializing()) {
            if (_thisThreadIsInitializingClass(cls)) {
                return;
            } else {
                waitForInitializeToComplete(cls);
                return;
            }
        }
        else if (cls->isInitialized()) {
     
            return;
        }
        else {
            _objc_fatal("thread-safe class init in objc runtime is buggy!");
        }
    }
    
    • 确保当前类的父类supercls已经初始化完成 -- 如果没有则通过_class_initialize(supercls)重新进入_class_initialize方法,初始化父类。

    • 根据当前类的初始化状态决定是否要发送初始化消息。如果当前类未初始化,则会向它发送一个setInitializing消息,将该类的元类的信息更改为CLS_INITIALIZING,并通过reallyInitialize标识来与Initializing区分。_setThisThreadIsInitializingClass记录当前线程正在初始化当前类,当前线程可以向该类发送消息,而其他线程则需要等待。

    • 通过objc_msgSend发送消息实现的,因此也拥有objc_msgSend带来的特性,也就是说子类会继承父类的方法实现,而分类的实现也会覆盖元类。

    • 完成initialize方法后,更新当前类的状态.如果父类已经完成初始化,则_finishInitializing立马更新,否则通过_finishInitializingAfter等父类完成后再更新。

    static void _finishInitializingAfter(Class cls, Class supercls)
    {
        PendingInitialize *pending;
     
        classInitLock.assertLocked();
     
        if (PrintInitializing) {
            _objc_inform("INITIALIZE: %s waiting for superclass +[%s initialize]",
                         cls->nameForLogging(), supercls->nameForLogging());
        }
     
        if (!pendingInitializeMap) {
            pendingInitializeMap = 
                NXCreateMapTable(NXPtrValueMapPrototype, 10);
            // fixme pre-size this table for CF/NSObject +initialize
        }
     
        pending = (PendingInitialize *)malloc(sizeof(*pending));
        pending->subclass = cls;
        pending->next = (PendingInitialize *)
            NXMapGet(pendingInitializeMap, supercls);
        NXMapInsert(pendingInitializeMap, supercls, pending);
    }
    
    • _finishInitializingAfter方法中,通过声明一个PendingInitialize类型的结构体pending来存储当前类与父类信息,并以父类superclskey值,以pendingvalue存储在pendingInitializeMap链表中。
    static void _finishInitializing(Class cls, Class supercls)
    {
        PendingInitialize *pending;
        classInitLock.assertLocked();
        assert(!supercls  ||  supercls->isInitialized());
     
        if (PrintInitializing) {
            _objc_inform("INITIALIZE: %s is fully +initialized",
                         cls->nameForLogging());
        }
     
        // mark this class as fully +initialized
        cls->setInitialized();
        classInitLock.notifyAll();
        _setThisThreadIsNotInitializingClass(cls);
     
        // mark any subclasses that were merely waiting for this class
        if (!pendingInitializeMap) return;
        pending = (PendingInitialize *)NXMapGet(pendingInitializeMap, cls);
        if (!pending) return;
     
        NXMapRemove(pendingInitializeMap, cls);
     
        // Destroy the pending table if it's now empty, to save memory.
        if (NXCountMapTable(pendingInitializeMap) == 0) {
            NXFreeMapTable(pendingInitializeMap);
            pendingInitializeMap = nil;
        }
     
        while (pending) {
            PendingInitialize *next = pending->next;
            if (pending->subclass) _finishInitializing(pending->subclass, cls);
            free(pending);
            pending = next;
        }
    }
    
    • _finishInitializing方法中会首先将当前类标记为已完成初始化状态Initialized,然后去读取pendingInitializeMap

    • 如果查找到该类对应的待处理子类,则将对应的消息移除并通过递归的方法将因当前类而被阻塞的子类标记为已完成初始化。

    • 如果是当前线程在进行初始化,则不做处理。

    • 如果是其他线程在进行初始化,则等其他线程完成后再返回,以保证线程安全。

    • 如果已经完成初始化,则不做处理。

    - (instancetype)init
     -(instancetype)init
    #if NS_ENFORCE_NSOBJECT_DESIGNATED_INITIALIZER
        NS_DESIGNATED_INITIALIZER
    #endif
        ;
    
    - (id)init {
        return _objc_rootInit(self);
    }
     
    id
    _objc_rootInit(id obj)
    {
        return obj;
    }
    
    • -(instancetype)init方法并没有次数的限制。

    • + initialize先于 -(instancetype)init执行。

    + load+ initialize的异同
    + load与+ initialize的异同
    要点
    1. 在加载阶段,如果类实现了load方法,那么系统就会调用它。分类里也可以定义此方法,类的load方法要比分类中的先调用。与其他方法不同,load方法不参与覆写机制。

    2. 首次使用某个类之前,系统会向其发生initialize消息。由于此方法遵从普通的覆写规则,所以通常应该在里面判断当前要初始化的是哪个类。

    3. load与initialize方法都应该实现的精简一些,这有助于保持应用程序的响应能力,也能减少引入“依赖环”的几率。

    4. 无法在编译期设定的全局常量,可以放在initialize方法里初始化。

    相关文章

      网友评论

        本文标题:52个有效方法(51) - 精简initialize与load的

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