OC 中的 load 与 initialize 方法
这里所要介绍的 load 与 initialize 方法,这两个是类方法,是系统的方法。我曾经见过有大神在自己的class中写了一个 initialize对象 方法,无知的我在那里绕了半天。
建议大家,从这里下载我的样例项目。
一定要记得下载loadAndinitialize。
一、先抛出问题
- 1、这两个方法的调用时机
- 2、有何异同
- 3、有何用途
二、load
在文件刚开始装载的时候被调用(在main方法被调用之前),并有且仅调用一次。
运行项目Load,打印结果如下:
2017-06-04 23:20:11.855869+0800 Load[7892:2777060] OtherObject
2017-06-04 23:20:11.856272+0800 Load[7892:2777060] ParentObject
2017-06-04 23:20:11.856298+0800 Load[7892:2777060] ChildObject
2017-06-04 23:20:11.856321+0800 Load[7892:2777060] ChildObject+Category
load 的执行顺序
- 1、相关联的class的执行顺序: 父类 --> 子类 --> 分类。
- 2、不相关联执行顺序:是根据 项目配置中的 Compile Sources 顺序决定的。(调换项目Load中的OtherObject的位置可以看看效果)
各位大神的建议
在 load类方法 中,尽量简洁,不要写复杂的逻辑。一般可以将 Swizzled 方法在 load类方法 中实现。
三、initialize
在当前class第一次被用到的时候被调用(第一次发送消息),同样有且仅调用一次。所有,也有很多大神说这个方法有懒加载的感觉,用到才会触发。不像 load类方法 那样,一旦装载就赶紧触发。
情景一
打开 项目initialize 直接运行,会发现没有打印日志。说明在没有给当前class发送消息,是不会触发 initialize类方法 的。
情景二
在 ViewController.m 文件中仅打开 test1 方法,运行项目,打印日志如下:
2017-06-05 00:15:06.312925+0800 Initialize[7959:2792250] ParentObject = ParentObject
2017-06-05 00:15:06.313506+0800 Initialize[7959:2792250] ChildObject = ChildObject
情景三
在 ViewController.m 文件中仅打开 test1 方法的前提下,将 ChildObject 中的 initialize类方法 注释掉,运行项目,打印日志如下:
2017-06-05 00:16:17.099422+0800 Initialize[7963:2793122] ParentObject = ParentObject
2017-06-05 00:16:17.099534+0800 Initialize[7963:2793122] ParentObject = ChildObject
情景二 与 情景三 同时说明: 父类中的 initialize类方法 会被子类触发。在子类中即使没有实现 initialize类方法 ,也会默认的去调用父类的 initialize类方法 。所以在实现 +initialize 方法的时候一定要做好判断,放在当前 Class 被继承导致一些重复的操作(在代码中已经做了判断)。
在进行之后的情景之前,记得将 ChildObject 中的 initialize类方法 打开。
情景四
在 ViewController.m 文件中仅打开 test2 方法,运行项目,打印日志如下:
2017-06-05 00:27:31.883299+0800 Initialize[7968:2795570] ParentObject = ParentObject
2017-06-05 00:27:31.883410+0800 Initialize[7968:2795570] ChildObject = ChildObject
2017-06-05 00:27:31.883463+0800 Initialize[7968:2795570] ------华丽的分割线------
情景五
在 ViewController.m 文件中仅打开 test3 方法,运行项目,打印日志如下:
2017-06-05 00:29:47.599994+0800 Initialize[7971:2796349] ParentObject = ParentObject
2017-06-05 00:29:47.600114+0800 Initialize[7971:2796349] ------华丽的分割线------
2017-06-05 00:29:47.600160+0800 Initialize[7971:2796349] ChildObject = ChildObject
情景四 与 情景五 同时说明: initialize类方法 有且仅调用一次。
细心的大神,应该发现了点漏洞了。会问 情景三 又是怎么回事? 在 ParentObject 类中明明被触发了两次。再仔细一看,其实有一个是子类触发的,看 self 的打印值是 ChildObject 就明白了。那一般应该怎么处理这种情况呢?解决方案是,在 ParentObject 类中的 initialize类方法 中作这样的判断即可:
+ (void)initialize {
NSLog(@"ParentObject = %@", self);
if (self == [ParentObject class]) {
// TODO: 做自己喜欢做的事
}
}
在 initialize类方法 中,一般都是作一些全局性的一次性设置。比如 UITabBarController 与 UINavigationController 全局的统一设置。
比如,我常用的:
+ (void)initialize
{
UINavigationBar *navigationBar = [UINavigationBar appearance];
[navigationBar setBackgroundImage:[UIImage imageWithColor:GlobalColor] forBarMetrics:UIBarMetricsDefault];
if ([UINavigationBar instancesRespondToSelector:@selector(setShadowImage:)]) {
[navigationBar setShadowImage:[UIImage imageWithColor:[UIColor clearColor]]];
}
// 统一修改控制器title的字体颜色
NSMutableDictionary* dictM = [NSMutableDictionary dictionary];
dictM[NSFontAttributeName] = Font(19);
dictM[NSForegroundColorAttributeName] = [UIColor whiteColor];
[navigationBar setTitleTextAttributes:dictM];
}
四、load 与 initialize 同时出现
打开 项目 02LoadandInitialize, 直接运行,打印日志如下:
2017-06-05 00:55:47.278158+0800 02LoadandInitialize[7976:2801773] initialize = LoadandInitializeObject
2017-06-05 00:55:47.278589+0800 02LoadandInitialize[7976:2801773] load = LoadandInitializeObject
两个问题来了:
- 1、没有在任何地方用到 LoadandInitializeObject ,initialize类方法 尽然被触发了,这是为什么?
- 2、仔细一看,initialize类方法 尽然在 load类方法 前面执行了,这又是为什么?
其实,在 项目 02LoadandInitialize 中就能找到答案。如果找不到原因,请直接打开 项目 03LoadandInitialize 运行一下,打印日志如下:
2017-06-05 01:04:30.237773+0800 03LoadandInitialize[7979:2804252] load 开始执行
2017-06-05 01:04:30.238219+0800 03LoadandInitialize[7979:2804252] initialize = LoadandInitializeObject
2017-06-05 01:04:30.238271+0800 03LoadandInitialize[7979:2804252] load = LoadandInitializeObject
2017-06-05 01:04:30.238348+0800 03LoadandInitialize[7979:2804252] load 执行即将结束
终于真相大白了,原来是在 load 方法中调用了[self class] 导致的。所以一般是强烈不推荐在 +load 方法中调用当前 Class 与其他 Class 的任何方法的。如果在当前的 Class 的 +load 中调用其它 Class 的方法,一旦出现问题,是很难排查的。
六、一个小总结
- 1、load 与 initialize 都是系统自动调用的,不要手动调用。
- 2、尽量不要手动的通过 super 在子类中调用父类的方法。
- 3、两个方法,有且仅会触发一次。 只是要注意 initialize 的 情景三 特例。
- 4、load 优先于 initialize 触发。
七、分类中重写 +load 与 +initialize
- 1. +load 方法不管在原生 Class 中还是分类中,一旦重写了都会被调用。
- 2. +initialize 方法如果在分类中重写了,那么原生 Class 中的就不会被调用。
具体的原因是两个方法的调用逻辑不一样。
+load 方法是在 dyld 加载的过程中被调用,调用的逻辑是一旦检测到+load 方法被重写,那么直接就调用。
+initialize 方法的调用逻辑与通常的消息发送机制一致,都是通过当前 Class 的 isa 来进行查找方法调用。其中 Class 的 isa 的值就是其对应的元类(meta-class)。
image.png为什么分类中重写了原生 Class 中的方法之后,原生 Class 中的方法失效?
对于一个 Class 来说,所有的分类方法都被放于原生 Class 方法的前面。可以看看下面这张图。
所以一旦分类中重写了原生类中的方法,那么原生类中的方法就永远没有机会被调用。