+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方法。

这里,我们修改下代码,调用[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 cachesreturn _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方法

+initialize方法的调用时机相关推荐

  1. 关于Activity onNewIntent方法的调用时机

    在官方API上的说明如下: http://developer.android.com/reference/android/app/Activity.html#onNewIntent(android.c ...

  2. IOS-layoutSubviews方法的调用时机

    IOS-layoutSubviews方法的调用时机 前言 layoutSubviews调用时机 setNeedsLayout 和 layoutIfNeeded addSubview 改变view的si ...

  3. Python面向对象程序设计中对象析构方法的调用时机

    开学第一课:一定不要这样问老师Python问题 中国大学MOOC"Python程序设计基础"第6次开课时间 董付国老师Python系列教材推荐与选用参考 ============= ...

  4. SAP Spartacus UserService.get方法的调用时机

    在cart-page-layout-handler.ts: selective-cart.service.ts: 上图48行this.userService.get()返回一个Observable: ...

  5. iOS load方法和initialize方法的异同

    对于OC中的类来说,在runtime中会有两个方法被调用: +load +initialize 这两个方法看起来都是在类初始的时候调用的,但其实还是有一些异同,从而可以用来做一些行为. +load 首 ...

  6. 对应用程序启动时所有方法的调用顺序分析

    一个应用程序的启动过程要包括代理的创建,控制器的加载和控制器view的加载,这其中有很多关于生命周期的方法,每个方法都是有先后顺序的,如果调用顺序拿不准,或者某段代码写的方法不恰当,就会遇到各种奇葩问 ...

  7. 【转】 onNewIntent调用时机

    onNewIntent调用时机 在IntentActivity中重写下列方法:onCreate onStart onRestart  onResume  onPause onStop onDestro ...

  8. initialize方法与load方法比较

    load方法和initialize方法类似点 1. 都只会调用一次2. 父类在子类之前加载 复制代码 不同点在于: 1. 加载时间不同,load方法在main()函数前进行调用,initialize在 ...

  9. Android应用开发—setResult()的调用时机

    本文转载自setResult()的调用时机,此处做了重新的排版,只是感觉markdown的排版比较好看些,侵删. 今天遇到这样一个问题,我在Activity-A中用startActivityForRe ...

  10. aop统计请求数量_使用SpringAOP获取一次请求流经方法的调用次数和调用耗时

    引语### 作为工程师,不能仅仅满足于实现了现有的功能逻辑,还必须深入认识系统.一次请求,流经了哪些方法,执行了多少次DB操作,访问了多少次文件操作,调用多少次API操作,总共有多少次IO操作,多少C ...

最新文章

  1. OpenWrite 公开内测,做最懂你的技术自媒体管理平台!
  2. 工程项目管理丁士昭第二版_2021年软考系统集成项目管理工程师知识点预习第十四章第二节...
  3. Windbg dump分析 学习总结
  4. Java开发人员的十大戒律
  5. Datawhale-零基础入门NLP-新闻文本分类Task06
  6. soapui返回值类型都有哪些_滚珠丝杠的常用类型都有哪些?
  7. docker删除私有仓库中的镜像
  8. overflow-x后覆盖滚动条
  9. 51nod 1174 区间最大值(RMQ and 线段树)
  10. Kodak Preps 8 for Mac中文破解版永久激活教程
  11. 学习java之前应该先了解哪些知识?
  12. [教程] 基于时间盲注的python3脚本编写
  13. linux网络编程相关函数(一)
  14. 语义网络,语义网,链接数据和知识图谱
  15. Lasso 的 python实现
  16. 三国杀代码12武将C++
  17. 对持久层、持久性、持久化的讨论
  18. 常用的连续概率分布汇总
  19. python 解析类似 ‘\xe4\xb8\xad\xe5\x9b\xbd‘的unicode码为汉字
  20. iOS从健康app中获取步数信息

热门文章

  1. php无法运行,php不执行是什么原因造成的
  2. 优盘里文件夹变成html,U盘里面的文件跟文件夹突然乱码了怎么办
  3. BC20+MQTT+OneNet 订阅主题总是error
  4. curl myip.ipip.net curl ip.cn curl cip.cc
  5. QFT的问世 高斯定理
  6. 计算机设置ip后提示未识别网络连接,Win7出现提示“未识别的网络无Internet访问”怎么解决?...
  7. Google Sketchup论坛
  8. 运用GoogleSketchUp创作城市雕塑
  9. 一份无锡工程师的分享
  10. DATE_FORMAT函数用法