美文网首页面试
Category底层实现分析3 - +initialize方法

Category底层实现分析3 - +initialize方法

作者: Jacob_LJ | 来源:发表于2018-08-27 23:55 被阅读61次

    注:分析参考 MJ底层原理班 内容,本着自己学习原则记录

    本文使用的源码为objc4-723

    1 +initialize 方法是什么,作用是什么,什么时候调用

    +initialize 官方解析,基本内容如下:

    1. +initialize 方法会在类第一次接收到消息时调用
    2. 优先调用父类的 initialize 方法
    3. +initialize的调用是线程安全的,其他线程需要调用 initialize 方法时会被阻塞直到首次initialize调用完成
    4. 如果子类没有实现 +initialize,则可以多次调用父类的 +initialize 方法
    5. 如果想保护该类不被多次调用 +initialize,可以参考一下方式构建 +initialize 方法:
      + (void)initialize {
        if (self == [ClassName self]) {
          // ... do the initialization ...
        }
      }
      
    6. 因为 +initialize的调用是以阻塞方式确保安全调用,所以需要确保 +initialize 方法不能执行耗时的导致死锁的行为
    7. 每个类+initialize只调用一次。如果要对类和类的类别执行独立初始化,则应实现load方法(官方解析分类的load方法

    2 +initialize 调用顺序及代码验证

    调用顺序总结:
    先调用父类的+initialize,再调用子类的+initialize
    (先初始化父类,再初始化子类,每个类只会初始化1次)

    下述测试代码基本情况描述:

    • 有两个类 Person、Student,Student 是Person的子类,其中它们都有两个分类,分别是 Test1 和 Test2
    • 所有的类和分类中的.m文件都实现了 +initialize 方法

    2.1 +initialize 的调用方式是通过 objc_msgSend 的方法(即 isa 寻址方式)调用的

    1. 从优先调用 Person 的分类 Test2 中的+initialize 方法可知,+initialize 的调用方式是通过 objc_msgSend 的方法(即 isa 寻址方式)调用的
    2. 前面分析 Category 的 runtime时,有说过 runtime启动时会将 Category 中的信息合并到类信息中,而且是通过向前插入的方式并入到相应的方法列表中的,也就是说,当类调用方法时,会有先调用 Category 同名的类方法。

    2.2 每个类只会初始化1次(只调用+initialize 方法一次)

    1. 不管后面给 Person 发送多少次消息,+initialize 方法只会调用一次

    2.3 调用子类+initialize之前会先调用父类的

    2.4 如果子类没有重写+initialize 方法,那么父类可能被调用多次

    • 将 Student 类及其分类 Test1和 Test2中的+initialize 方法注释掉


    1. 如果不想父类的初始化方法被调用多次,可以参考第1大点的第1小点,采用如下方式保护
      + (void)initialize {
        if (self == [ClassName self]) {
          // ... do the initialization ...
        }
      }
      
    2. 其实想想也知道,为啥父类的方法会被调用多次的原因:
    • 首先,+initialize 的调用方式是通过 objc_msgSend 方式调用的
    • 有因为 Student 继承自 Person,那么就说明子类可以调用父类的类方法
    • 所以,当子类Student第一次接收消息 alloc时,系统会让主动调用 Student 的+initialize 方法,当发现 Student 的元类对象没有该方法时,就会通过 super 指针父类的元类对象中查找,发现 Person 中有+initialize 方法,就调用。

    3 既然+initialize 是通过 objc_msgSend 的方式内部主动被系统调用的,那 runtime 中的objc_msgSend源码应该是可以找到系统主动调用+initialize 方法的证明才对

    3.1 objc_msgSend

    • 实际是,runtime 的objc_msgSend 方法是通过汇编写的,半开源,这样很难发现系统主动调用 +initialize 的证据(等能力升级后再分析汇编吧
    • 但是,我们也可以想想,既然 objc_msgSend 内部是主动调用的+initialize 方法的(目前也是根据前面的方式而猜想的),那它一定会去查找对应的方法不是吗?那就说内部一定有一个逻辑是处理查找+initialize方法才对。回到 objc_msgSend 方法的开头注释描述中发现:有一个可疑地方IMP objc_msgLookup(id self, SEL _cmd, ...);

    3.2 objc_msgLookup

    很不巧,它也是汇编实现的


    3.3 _class_lookupMethodAndLoadCache3

    但是,通过上述方法搜到的这篇文章在 objc_msgSend 的汇编内找到了继续思路_class_lookupMethodAndLoadCache3

    1. _class_lookupMethodAndLoadCache3的作用就是:
      就是直接调用了_class_lookupMethodAndLoadCache3 来查找方法并缓存到struct objc_class中的cache中,最后再返回IMP类型。可见,就是查找方法,我们知道+initialize 就是一个类方法,调用它时候,就是要通过元类对象找到它的 SEL 嘛。
    image.png

    3.4 lookUpImpOrForward

    /***********************************************************************
    * lookUpImpOrForward.
    * The standard IMP lookup. 
    * initialize==NO tries to avoid +initialize (but sometimes fails)
    * cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)
    * Most callers should use initialize==YES and cache==YES.
    * inst is an instance of cls or a subclass thereof, or nil if none is known. 
    *   If cls is an un-initialized metaclass then a non-nil inst is faster.
    * May return _objc_msgForward_impcache. IMPs destined for external use 
    *   must be converted to _objc_msgForward or _objc_msgForward_stret.
    *   If you don't want forwarding at all, use lookUpImpOrNil() instead.
    **********************************************************************/
    IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                           bool initialize, bool cache, bool resolver)
    {
        IMP imp = nil;
        bool triedResolver = NO;
    
        runtimeLock.assertUnlocked();
    
        // Optimistic cache lookup
        if (cache) {
            imp = cache_getImp(cls, sel);
            if (imp) return imp;
        }
    
        // runtimeLock is held during isRealized and isInitialized checking
        // to prevent races against concurrent realization.
    
        // runtimeLock is held during method search to make
        // method-lookup + cache-fill atomic with respect to method addition.
        // Otherwise, a category could be added but ignored indefinitely because
        // the cache was re-filled with the old value after the cache flush on
        // behalf of the category.
    
        runtimeLock.read();
    
        if (!cls->isRealized()) {
            // Drop the read-lock and acquire the write-lock.
            // realizeClass() checks isRealized() again to prevent
            // a race while the lock is down.
            runtimeLock.unlockRead();
            runtimeLock.write();
    
            realizeClass(cls);
    
            runtimeLock.unlockWrite();
            runtimeLock.read();
        }
    
        if (initialize  &&  !cls->isInitialized()) {
            runtimeLock.unlockRead();
            _class_initialize (_class_getNonMetaClass(cls, inst));
            runtimeLock.read();
            // 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
        }
    
        
     retry:    
        runtimeLock.assertReading();
    
        // Try this class's cache.
    
        imp = cache_getImp(cls, sel);
        if (imp) goto done;
    
        // Try this class's method lists.
        {
            Method meth = getMethodNoSuper_nolock(cls, sel);
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, cls);
                imp = meth->imp;
                goto done;
            }
        }
    
        // Try superclass caches and method lists.
        {
            unsigned attempts = unreasonableClassCount();
            for (Class curClass = cls->superclass;
                 curClass != nil;
                 curClass = curClass->superclass)
            {
                // Halt if there is a cycle in the superclass chain.
                if (--attempts == 0) {
                    _objc_fatal("Memory corruption in class list.");
                }
                
                // Superclass cache.
                imp = cache_getImp(curClass, sel);
                if (imp) {
                    if (imp != (IMP)_objc_msgForward_impcache) {
                        // Found the method in a superclass. Cache it in this class.
                        log_and_fill_cache(cls, imp, sel, inst, curClass);
                        goto done;
                    }
                    else {
                        // Found a forward:: entry in a superclass.
                        // Stop searching, but don't cache yet; call method 
                        // resolver for this class first.
                        break;
                    }
                }
                
                // Superclass method list.
                Method meth = getMethodNoSuper_nolock(curClass, sel);
                if (meth) {
                    log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                    imp = meth->imp;
                    goto done;
                }
            }
        }
    
        // No implementation found. Try method resolver once.
    
        if (resolver  &&  !triedResolver) {
            runtimeLock.unlockRead();
            _class_resolveMethod(cls, sel, inst);
            runtimeLock.read();
            // Don't cache the result; we don't hold the lock so it may have 
            // changed already. Re-do the search from scratch instead.
            triedResolver = YES;
            goto retry;
        }
    
        // No implementation found, and method resolver didn't help. 
        // Use forwarding.
    
        imp = (IMP)_objc_msgForward_impcache;
        cache_fill(cls, sel, imp, inst);
    
     done:
        runtimeLock.unlockRead();
    
        return imp;
    }
    

    其中一段代码:

    • 判断:如果该类 是需要初始化 && 该类还没初始化过
    • 那么让调用类的初始化方法
        //< 如果该类 是需要初始化 && 该类还没初始化过
        if (initialize  &&  !cls->isInitialized()) {
            runtimeLock.unlockRead();
            //< 那么让调用类的初始化方法
            _class_initialize (_class_getNonMetaClass(cls, inst));
            runtimeLock.read();
            // 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
        }
    
    1. 证明了类的初始化只会调用一次if (initialize && !cls->isInitialized())

    3.5 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;
    
        // 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);
    
            if (MultithreadedForkChild) {
                // LOL JK we don't really call +initialize methods after fork().
                performForkChildInitialize(cls, supercls);
                return;
            }
            
            // 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: thread %p: calling +[%s initialize]",
                             pthread_self(), cls->nameForLogging());
            }
    
            // Exceptions: A +initialize call that throws an exception 
            // is deemed to be a complete and successful +initialize.
            //
            // Only __OBJC2__ adds these handlers. !__OBJC2__ has a
            // bootstrapping problem of this versus CF's call to
            // objc_exception_set_functions().
    #if __OBJC2__
            @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!");
        }
    }
    

    其中,递归初始化父类

        // 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); //< 递归调用原来的方法,处理父类情况
        }
    

    之后就是调用初始化方法了 callinitialize()方法

     callInitialize(cls);
    
    1. 证明了调用初始化方法前,会先调用父类的方法递归处理父类逻辑

    3.6 callInitialize()

    1. 这也证明了第2点的2.1小点的观点,+initialize方法确实是系统主动通过 objc_msgSend 方式调用的

    4 initialize 方法和 load 方法的区别

    +initialize和+load的很大区别是,

    1. +initialize是通过objc_msgSend进行调用的,所以有以下特点
      • 如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次)
      • 如果分类实现了+initialize,就覆盖类本身的+initialize调用
    2. load 方法是在mian 函数前系统主动调用的,而 initialize 方法是类第一次接受消息时调用的
    3. 类与分类的 load 方法是独立调用的,而 initialize 方法则不是(已为第1点的特性)

    文/Jacob_LJ(简书作者)
    PS:如非特别说明,所有文章均为原创作品,著作权归作者所有,转载需联系作者获得授权,并注明出处,所有打赏均归本人所有!

    相关文章

      网友评论

        本文标题:Category底层实现分析3 - +initialize方法

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