iOS的 +load 和 +initialize 比较
该文为load和initialize方法的简单记录,部分总结自如下文章,感谢作者分享:
+load方法
load方法的特点
当 类 或者 类别 加入Runtime中时(就是被引用的时候)就会执行 load 方法。
1. 父类 先于 子类执行;
2. 子类未实现,不会 调用父类的 load 方法;
3. 同级别有多个类时(如 B、B1类都继承于A类),则其 load 方法执行顺序 依赖于 Compile Sources 中的顺序;
4. 类的 load 方法 先于 类别执行,无论类别是哪个类的;
5. 有多个类别,无论类别是哪个类的,其执行顺序都 依赖于 Compile Sources 中的顺序。
demo验证特点
有如下几个类,他们的关系是:
XQC 类 继承于 XQB 类;而 XQB 类继承于 XQA 类。
1、在每个类的 .m 文件中,添加如下代码,来观察执行顺序:
+(void)load
{
NSLog(@"%s",__FUNCTION__);
}
输出结果如下, 符合特点 父类先于子类执行
2018-06-25 15:52:53.469527+0800 TestLoadFunc[11712:298527] +[XQA load]
2018-06-25 15:52:53.471045+0800 TestLoadFunc[11712:298527] +[XQB load]
2018-06-25 15:52:53.471453+0800 TestLoadFunc[11712:298527] +[XQC load]
2、屏蔽 XQB 类 中的 load方法,输出结果如下,符合特点 子类未实现,不会调用父类的 load 方法。
2018-06-25 15:52:53.469527+0800 TestLoadFunc[11712:298527] +[XQA load]
2018-06-25 15:52:53.471453+0800 TestLoadFunc[11712:298527] +[XQC load]
3、还原 XQB 类 中的 load 方法,添加XQB1类,继承于XQA,类,并且为XQB1类添加 load 方法。
输出结果如下:
2018-06-25 16:05:42.086176+0800 TestLoadFunc[11873:311360] +[XQA load]
2018-06-25 16:05:42.086941+0800 TestLoadFunc[11873:311360] +[XQB1 load]
2018-06-25 16:05:42.087752+0800 TestLoadFunc[11873:311360] +[XQB load]
2018-06-25 16:05:42.088013+0800 TestLoadFunc[11873:311360] +[XQC load]
查看Compile Sources中的顺序为 XQB1 类在 XQB 类之前,所以符合特点 同级别有多个类时,则其load方法执行顺序依赖于 Compile Sources 中的顺序。
屏幕快照 2018-06-25 下午4.08.10.png
4、分别为 XQA、XQB、XQC类添加对应的类别 XQA+ACategory、XQB+BCategory、XQB+BCategory,分别为类别添加 load 方法,来观察执行顺序:
2018-06-25 16:05:42.086176+0800 TestLoadFunc[11873:311360] +[XQA load]
2018-06-25 16:05:42.086941+0800 TestLoadFunc[11873:311360] +[XQB1 load]
2018-06-25 16:05:42.087752+0800 TestLoadFunc[11873:311360] +[XQB load]
2018-06-25 16:05:42.088013+0800 TestLoadFunc[11873:311360] +[XQC load]
2018-06-25 16:05:42.088123+0800 TestLoadFunc[11873:311360] +[XQC(CCategory) load]
2018-06-25 16:05:42.088309+0800 TestLoadFunc[11873:311360] +[XQA(ACategory) load]
2018-06-25 16:05:42.088408+0800 TestLoadFunc[11873:311360] +[XQB(BCategory) load]
而查看Compile Sources中的顺序为:
屏幕快照 2018-06-25 下午4.14.07.png
所以,符合特点
类的 load 方法 先于 类别执行,无论类别是哪个类的。
和
有多个类别,无论类别是哪个类的,其执行顺序都依赖于 Compile Sources 中的顺序。
load 本质
load 方法是在运行时被执行的(main函数之前),其调用栈如下(可断点调试):
_ load_images
//加载类和类别的load方法
└── load_images_nolock
//执行所有load方法
└── call_load_methods
而在 load_images_nolock 方法中,则调用了 prepare_load_methods,其执行了两个方法:
_ prepare_load_methods
//先将需要执行 load 的 class 添加到一个全局列表里 (loadable_class)
└── schedule_class_load
//然后将需要执行 load 的 category 添加到另一个全局列表里(loadable_category)
└── add_category_to_loadable_list
而在 schedule_class_load 方法中,确保先将父类添加到列表中。
static void schedule_class_load(class_t *cls)
{
assert(isRealized(cls)); // _read_images should realize
if (cls->data->flags & RW_LOADED) return;
//确保先将父类添加到全局列表里 (loadable_class)
class_t *supercls = getSuperclass(cls);
if (supercls) schedule_class_load(supercls);
//再将当前类添加到全局列表里 (loadable_class)
add_class_to_loadable_list((Class)cls);
changeInfo(cls, RW_LOADED, 0);
}
然后,在执行 call_load_methods 方法时:
_ call_load_methods
//先遍历 loadable_classes 列表中的类,执行 load 方法。
└── call_class_loads
//然后再遍历 loadable_category 列表中的分类 ,执行 load 方法。
└── call_category_loads
而且在 call_class_loads 中,执行load方法的代码为:
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
IMP load_method = classes[i].method;
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", _class_getName(cls));
}
(*load_method) ((id) cls, SEL_load);
}
所以由上可知,load 方法的本质:直接执行函数指针。
所以,load 方法不会执行发送消息objc_msgSend那一套流程,这也解释了,为何子类、分类的load 方法不会覆盖父类的load 方法。
+initialize 方法
initialize方法的特点
在类、或者其子类,接收到第一条消息之前被执行。
1. 类引入项目,但未使用,则不会执行;
2. 父类 先于 子类执行;(同 load方法特点)
3. 子类未实现,则 会调用 父类的initialize方法;
4. 类的分类实现了 initialize方法,则 会覆盖 类中的 initialize方法;
5. 类存在多个分类,且都实现了 initialize 方法,则依赖于 Compile Sources 中的顺序,执行顺序中 最后一个分类 的 initialize 方法。
demo验证特点
有如下几个类,他们的关系是:
XQC 类 继承于 XQB 类;而 XQB 类继承于 XQA 类。
1、在每个类的 .m 文件中,添加如下代码,来观察执行顺序:
+ (void)initialize{
NSLog(@"%s",__FUNCTION__);
}
运行后,无任何打印信息。
符合特点,引入项目,没有使用,不会执行
2、然后初始化一个 XQC 类的对象:[XQC new];
运行后,输出结果为:
2018-06-25 17:06:13.430962+0800 TestLoadFunc[12333:367505] +[XQA initialize]
2018-06-25 17:06:13.431099+0800 TestLoadFunc[12333:367505] +[XQB initialize]
2018-06-25 17:06:13.431350+0800 TestLoadFunc[12333:367505] +[XQC initialize]
符合特点:父类先于子类执行
3、屏蔽 XQC 类的initialize方法,并将 XQB 类的initialize方法改为:
+ (void)initialize{
NSLog(@"%@ %s",self,__FUNCTION__);
}
运行发现打印为:
2018-06-25 17:17:27.425518+0800 TestLoadFunc[12486:377950] +[XQA initialize]
2018-06-25 17:17:31.276542+0800 TestLoadFunc[12486:377950] XQB +[XQB initialize]
2018-06-25 17:17:35.670737+0800 TestLoadFunc[12486:377950] XQC +[XQB initialize]
可以看出,打印了两次 +[XQB initialize],按照官方解释
The superclass implementation may be called multiple times if subclasses do not implement initialize—the runtime will call the inherited implementation—or if subclasses explicitly call [super initialize]. If you want to protect yourself from being run multiple times, you can structure your implementation along these lines:
+ (void)initialize {
if (self == [ClassName self]) {
// ... do the initialization ...
}
}
分析原因:因为 XQC 类继承自 XQB 类,子类未实现,则会调用父类的initialize方法,而XQB 类初始化时,第一次收到消息,也会调用自身的 initialize 方法,所以打印结果第一次是XQB 类调用了 initialize 方法,然后才是子类 XQC 类调用了 initialize 方法。
4、将XQC 类的initialize方法还原,为 XQB 类添加对应的类别 XQB+BCategory,并为类别添加initialize方法,运行来观察执行顺序:
2018-06-25 17:30:02.710611+0800 TestLoadFunc[12650:390749] +[XQA initialize]
2018-06-25 17:30:02.710751+0800 TestLoadFunc[12650:390749] +[XQB(BCategory) initialize]
2018-06-25 17:30:02.710855+0800 TestLoadFunc[12650:390749] +[XQC initialize]
符合特点:类的分类实现了 initialize方法,则会覆盖类中的 initialize方法。
5、再为 XQB 类添加一个类别 XQB+BCategory1,并为类别添加initialize方法,运行来观察执行顺序:
2018-06-25 17:37:19.533536+0800 TestLoadFunc[12734:397777] +[XQA initialize]
2018-06-25 17:37:19.533884+0800 TestLoadFunc[12734:397777] +[XQB(BCategory1) initialize]
2018-06-25 17:37:19.534419+0800 TestLoadFunc[12734:397777] +[XQC initialize]
观察Compile Sources 中的顺序为:
屏幕快照 2018-06-25 下午5.37.46.png
XQB+BCategory1.m 排在 XQB+BCategory.m 之后,所以符合特点:类存在多个分类,且都实现了
initialize 方法,则依赖于 Compile Sources 中的顺序,执行顺序中 最后一个分类 的 initialize 方法
initialize 方法的本质
查看 _class_initialize 源码
__private_extern__ void _class_initialize(Class cls)
{
Class supercls;
BOOL reallyInitialize = NO;
// Get the real class from the metaclass. The superclass chain
// hangs off the real class only.
cls = _class_getNonMetaClass(cls);
// Make sure super is done initializing BEFORE beginning to initialize cls.
// See note about deadlock above.
supercls = _class_getSuperclass(cls);
if (supercls && !_class_isInitialized(supercls)) {
_class_initialize(supercls);
}
// Try to atomically set CLS_INITIALIZING.
monitor_enter(&classInitLock);
if (!_class_isInitialized(cls) && !_class_isInitializing(cls)) {
_class_setInitializing(cls);
reallyInitialize = YES;
}
monitor_exit(&classInitLock);
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);
// 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: calling +[%s initialize]",
_class_getName(cls));
}
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
if (PrintInitializing) {
_objc_inform("INITIALIZE: finished +[%s initialize]",
_class_getName(cls));
}
// Done initializing.
......
}
分析源码:
1、优先执行父类的 initialize 方法。
通过 _class_getSuperclass取出父类,递归调用父类的 initialize 方法。
2、initialize 方法最终是通过 objc_msgSend 来执行的。