+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
方法。
这里,我们修改下代码,调用[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
_class_lookupMethodAndLoadCache3
方法,最终调用是汇编层调用,也就是说我们的代码调用,代码调用前是需要这个类初始化完毕的。这里就是我们要找的入口。objc_loadWeakRetained
、weak_register_no_lock
因为这两个方法会调用SEL_retainWeakReference
这个selector,所以要判断是否已经初始化完毕。methodForSelector
、methodFor
、instanceMethodFor
这三个方法也是获取IMP
的,所以也是需要这个类初始化完毕,否则通过Runtime增加的一些方法,可能无法获取到。
总结
本文主要通过官方文档、例子以及Runtime源码,分析了+initialize
方法的调用,总结如下:
- 当代码执行到一个类第一次调用方法时,会调用这个类的
+initialize
方法 - 在调用自身类的
+initialize
方法之前,会判断其父类链上是否有类还没有执行+initialize
方法,如果没有执行,那么执行。所以所有父类的+initialize
方法都执行在前,子类的+initialize
执行在后。 - 如果一个类有多个分类都实现了
+initialize
方法,那么会执行编译顺序的最后一个分类实现的+initialize
方法 - 当一个类实现了
+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方法的调用时机相关推荐
- 关于Activity onNewIntent方法的调用时机
在官方API上的说明如下: http://developer.android.com/reference/android/app/Activity.html#onNewIntent(android.c ...
- IOS-layoutSubviews方法的调用时机
IOS-layoutSubviews方法的调用时机 前言 layoutSubviews调用时机 setNeedsLayout 和 layoutIfNeeded addSubview 改变view的si ...
- Python面向对象程序设计中对象析构方法的调用时机
开学第一课:一定不要这样问老师Python问题 中国大学MOOC"Python程序设计基础"第6次开课时间 董付国老师Python系列教材推荐与选用参考 ============= ...
- SAP Spartacus UserService.get方法的调用时机
在cart-page-layout-handler.ts: selective-cart.service.ts: 上图48行this.userService.get()返回一个Observable: ...
- iOS load方法和initialize方法的异同
对于OC中的类来说,在runtime中会有两个方法被调用: +load +initialize 这两个方法看起来都是在类初始的时候调用的,但其实还是有一些异同,从而可以用来做一些行为. +load 首 ...
- 对应用程序启动时所有方法的调用顺序分析
一个应用程序的启动过程要包括代理的创建,控制器的加载和控制器view的加载,这其中有很多关于生命周期的方法,每个方法都是有先后顺序的,如果调用顺序拿不准,或者某段代码写的方法不恰当,就会遇到各种奇葩问 ...
- 【转】 onNewIntent调用时机
onNewIntent调用时机 在IntentActivity中重写下列方法:onCreate onStart onRestart onResume onPause onStop onDestro ...
- initialize方法与load方法比较
load方法和initialize方法类似点 1. 都只会调用一次2. 父类在子类之前加载 复制代码 不同点在于: 1. 加载时间不同,load方法在main()函数前进行调用,initialize在 ...
- Android应用开发—setResult()的调用时机
本文转载自setResult()的调用时机,此处做了重新的排版,只是感觉markdown的排版比较好看些,侵删. 今天遇到这样一个问题,我在Activity-A中用startActivityForRe ...
- aop统计请求数量_使用SpringAOP获取一次请求流经方法的调用次数和调用耗时
引语### 作为工程师,不能仅仅满足于实现了现有的功能逻辑,还必须深入认识系统.一次请求,流经了哪些方法,执行了多少次DB操作,访问了多少次文件操作,调用多少次API操作,总共有多少次IO操作,多少C ...
最新文章
- OpenWrite 公开内测,做最懂你的技术自媒体管理平台!
- 工程项目管理丁士昭第二版_2021年软考系统集成项目管理工程师知识点预习第十四章第二节...
- Windbg dump分析 学习总结
- Java开发人员的十大戒律
- Datawhale-零基础入门NLP-新闻文本分类Task06
- soapui返回值类型都有哪些_滚珠丝杠的常用类型都有哪些?
- docker删除私有仓库中的镜像
- overflow-x后覆盖滚动条
- 51nod 1174 区间最大值(RMQ and 线段树)
- Kodak Preps 8 for Mac中文破解版永久激活教程
- 学习java之前应该先了解哪些知识?
- [教程] 基于时间盲注的python3脚本编写
- linux网络编程相关函数(一)
- 语义网络,语义网,链接数据和知识图谱
- Lasso 的 python实现
- 三国杀代码12武将C++
- 对持久层、持久性、持久化的讨论
- 常用的连续概率分布汇总
- python 解析类似 ‘\xe4\xb8\xad\xe5\x9b\xbd‘的unicode码为汉字
- iOS从健康app中获取步数信息
热门文章
- php无法运行,php不执行是什么原因造成的
- 优盘里文件夹变成html,U盘里面的文件跟文件夹突然乱码了怎么办
- BC20+MQTT+OneNet 订阅主题总是error
- curl myip.ipip.net curl ip.cn curl cip.cc
- QFT的问世 高斯定理
- 计算机设置ip后提示未识别网络连接,Win7出现提示“未识别的网络无Internet访问”怎么解决?...
- Google Sketchup论坛
- 运用GoogleSketchUp创作城市雕塑
- 一份无锡工程师的分享
- DATE_FORMAT函数用法