+initialize方法的调用时机

+initialize方法的调用时机

一个类或者它的子类收到第一条消息(手写代码调用,+load方法不算)之前调用,可以做一些初始化的工作。但该类的+initialize的方法调用,在其父类之后。

Runtime运行时以线程安全的方式将+initialize消息发送给类。也就是说,当一个类首次要执行手动调用的代码之前,会等待+initialize方法执行完毕后,再调用该方法。

这里需要注意的一点:

当子类没有实现+initialize或者子类在+initialize中显式的调用了[super initialize],那么父类的+initialize方法会被调用多次。如果希望避免某一个类中的+initialize方法被调用过多次,可以使用下面的方法来实现:

+ (void)initialize {
  if (self == [ClassName self]) {
    // ... do the initialization ...
  }
}

因为+initialize是以阻塞方式调用的,所以很重要的一点就是将方法实现限制为可能最小的工作量。

Demo演示

接下来我们写一个demo来验证一下,首先创建一个Person类,和Person的两个分类Test1,Test2。

@interface Person : NSObject

@end

@implementation Person

+ (void)initialize
{
    NSLog(@"---- %p %s", self, __FUNCTION__);
}

@end


@interface Person (Test1)

@end

@implementation Person (Test1)

+ (void)initialize
{
    NSLog(@"---- %p %s", self, __FUNCTION__);
}

@end


@interface Person (Test2)

@end

@implementation Person (Test2)

+ (void)initialize
{
    NSLog(@"---- %p %s", self, __FUNCTION__);
}

@end

然后新建一个Student类继承Person类,并实现Student的两个分类Test1,Test2。

@interface Student : Person

@end

@implementation Student

+ (void)initialize
{
    NSLog(@"---- %p %s", self, __FUNCTION__);
}

@end


@interface Student (Test1)

@end

@implementation Student (Test1)

+ (void)initialize
{
    NSLog(@"---- %p %s", self, __FUNCTION__);
}

@end


@interface Student (Test2)

@end

@implementation Student (Test2)

+ (void)initialize
{
    NSLog(@"---- %p %s", self, __FUNCTION__);
}

@end

然后我们什么都不调用,直接运行程序,那应该也不会打印任何信息。

然后,我们调用[Person alloc]方法,再次运行,查看结果。

---- 0x10f53ef60 +[Person(Test2) initialize]

这里的输出分类的+initialize方法,查看一下Compile Sources,发现Person(Test2)分类在最后编译,所以调用的是它实现的+initialize方法。

image

这里,我们修改下代码,调用[Student alloc],然后看下运行结果。

---- 0x10c08af60 +[Person(Test2) initialize]
---- 0x10c08afb0 +[Student(Test1) initialize]

这里发现,Student调用+initialize方法时是会先调用父类的+initialize方法(如果代码中没有写父类的调用代码)。

然后我们继续新建一个HighSchoolStudent继承Student,什么都不实现,同时调用[HighSchoolStudent alloc],查看结果。

---- 0x101070048 +[Person(Test2) initialize]
---- 0x101070098 +[Student(Test1) initialize]
---- 0x10106fff8 +[Student(Test1) initialize]

确实这样会调用两次+[Student(Test1) initialize]方法。

[HighSchoolStudent initialize]的调用,是该类对象通过isa指针找到元类对象,在元类已缓存的方法列表中查方法,如果有就调用;如果没有就查找方法列表,如果还没有找到,那么就去类的父类的元类对象中查找,找到就调用。如果没有就递归下去直到NSObject的元类对象。所以找到了Student的元类对象,执行了initialize方法。

源码分析

我们可以在Runtime的源码中,分析一下调用+initialize方法的实现。首先一个类要调用方法时肯定是调用类方法,那么就先从class_getInstanceMethod方法入手查看吧。

/***********************************************************************
* class_getInstanceMethod.  Return the instance method for the
* specified class and selector.
**********************************************************************/
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);
}

这里可以看到基本上就是调用了lookUpImpOrNil函数,而且函数列表中有注释标记了第四个参数/*initalize*/。那我们继续看。

/***********************************************************************
* lookUpImpOrNil.
* Like lookUpImpOrForward, but returns nil instead of _objc_msgForward_impcache
**********************************************************************/
IMP lookUpImpOrNil(Class cls, SEL sel, id inst, 
                   bool initialize, bool cache, bool resolver)
{
    IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
    if (imp == _objc_msgForward_impcache) return nil;
    else return imp;
}

然后进入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)
{
    //  other code ..

    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
    }

    //  other code ..
}

这里判断如果需要初始化并且该类没有初始化,那么就进行类初始化。我们发现,我们从入口到这里找的话,其实传递进来的initialize其实是NO,不会进入初始化,但是咱们找到的地方是对的,我们继续来看代码,了解完实现之后,我们继续看哪里调用该函数时传递的initialize的值是YES。接下来我们进入_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) {
    	//	other code ..
#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;
    }
    
    //	other code ..
}

在这里我们看到,首先如果父类存在并且父类没有初始化过,那么调用_class_initialize函数来初始化父类,直到整条集成链上的所有类都初始化完毕。之后调用callInitialize函数来初始化自己。

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

这里很熟悉了,通过消息机制调用initialize方法。到这里能解释了,为什么initialize会调用父类的initialize方法。

回来我们继续说一下什么时候调用lookUpImpOrForward是传递的initialize的值为YES

  1. _class_lookupMethodAndLoadCache3方法,最终调用是汇编层调用,也就是说我们的代码调用,代码调用前是需要这个类初始化完毕的。这里就是我们要找的入口
  2. objc_loadWeakRetainedweak_register_no_lock因为这两个方法会调用SEL_retainWeakReference这个selector,所以要判断是否已经初始化完毕。
  3. methodForSelectormethodForinstanceMethodFor这三个方法也是获取IMP的,所以也是需要这个类初始化完毕,否则通过Runtime增加的一些方法,可能无法获取到。

总结

本文主要通过官方文档、例子以及Runtime源码,分析了+initialize方法的调用,总结如下:

  1. 当代码执行到一个类第一次调用方法时,会调用这个类的+initialize方法
  2. 在调用自身类的+initialize方法之前,会判断其父类链上是否有类还没有执行+initialize方法,如果没有执行,那么执行。所以所有父类的+initialize方法都执行在前,子类的+initialize执行在后。
  3. 如果一个类有多个分类都实现了+initialize方法,那么会执行编译顺序的最后一个分类实现的+initialize方法
  4. 当一个类实现了+initialize方法,但是子类没有实现+initialize或者子类在实现+initialize方法中显式的调用的[super initialize]方法,那么该类的+initialize方法会调用多次,如果不想该方法被多次调用,可以在该类的+initialize方法通过if (self = [ClassName self])进行判断来避免多次调用。

+load方法与+initialize方法的区别


调用方式

+load方法

根据函数地址直接调用

+initialize方法

是通过objc_msgSend调用


调用时机

+load方法

是Runtime加载类、分类的时候调用(如果不显式调用,只会调用一次)

+initialize方法

是类第一次接收到消息的时候调用(如果不显式调用,可能存在调用多次的风险)


调用顺序

+load方法
  • 先调用类的+load方法,再调用分类的+load方法
  • 有继承关系的类,先调用父类的+load,后调用子类的+load方法
  • 没有继承关系的类,会按照编译顺序来执行+load方法
  • 所有的分类,都按照编译顺序来执行+load方法
+initialize方法
  • 先调用父类的+initialize方法,后调用子类的+initialize方法
  • 如果一个类有分类,那么会调用最后编译的分类实现的+initialize方法
  • 通过消息机制调用,当子类没有实现+initialize方法时,会调用父类的initialize方法
发布了71 篇原创文章 · 获赞 34 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/TuGeLe/article/details/86611248