精简 initialize 与 load 实现代码
有时候,类必须先执行某些初始化操作,然后才能正常使用,在 OC 中,绝大部分类都继承自 NSObject 这个根类,而该类有两个方法. 可以用来实现这种初始化操作.
首先要说的是 load 方法,其原型如下:
+ (void)load;
对于加入运行期系统中的每个类 (class) 及 分类(category)来说,必定会调用此方法, 而且仅调用一次, 当包含类 或 分类的程序载入系统时, 就会执行此方法, 而这通常就是指应用程序启动的时候. 若程序是为 iOS 平台设计的,则肯定会在此执行, 如果分类和其所属的分类都定义了 load 方法,则先调用类里面的, 在调用分类里面的.
load 方法的问题在于,执行该方法时,运行期系统处于 '脆弱状态',在执行子类的 load 方法之前,必定会先执行所有 超类的 load 方法,而如果代码还还依赖了 其他程序库, 那么程序库里相关类的 load 方法也必定会先执行,然而,根据某个给定的程序库, 却无法判断出其中各个类的载入顺序. 因此,在 load 方法中使用其他类是不安全的. 例如:
#import<Foundation/Foundation.h>
#import "ClassA.h"
@interface ClassB : NSObject
@end
@implementation ClassB
+ (void)load{
NSLog(@"Loading ClassB");
ClassA * object = [ClassA new];
}
@end
此处使用 NSLog 没问题, 而且相关字符串也会照常记录, 因为 Foundation 框架肯定在运行 load 方法之前就已经载入系统了, 但是,在 ClassB 的 load 方法里面使用 ClassA 却不太安全, 因为无法确定在执行 ClassB 的 load 方法之前,ClassA 是不是已经加载好了, 可以想见: ClassA 这个类, 也许会在其 load 方法中执行某些重要操作, 只有执行完了这些操作之后, 该类实例才能正常使用;
有个重要的事情需要注意, 那就是 load 方法并不是像普通的方法 那样, 它并不遵从那套继承规则, 如果某个类本身没实现 load 方法, 那么不管其各级超类 是否实现此方法, 系统都不会调用, 此外,分类和其所属的类里面, 都可能出现 load 方法, 此时两种实现代码都会调用, 类的实现要比分类的实现先执行.
而且 load 方法务必实现的精简一些, 也就是要尽量减少其所执行的操作, 因为整个应用程序在执行 load 方法时都会阻塞, 如果 load 方法中包含繁杂的代码, 那么应用程序在执行期间机会变的无响应, 不要在里面等待锁, 也不要调用可能会加锁的方法, 总之, 能不做的事情就不要做,
实际上, 凡是想通过 load 方法在类加载之前执行某些任务的,基本都做的不太对,其真正的用途仅在于调试程序, 比如可以在分类里编写此方法, 用来判断分类是否已经正确载入系统, 也许此方法一度很有用, 但是完全可以说. 时下编写 OC 代码时, 不需要用它.
想执行与类相关的初始化操作, 还有一个办法, 就是覆写下列方法.
+ (void)initialize;
对于每个类来说, 该方法会在程序首次调用该类之前调用. 且只调用一次. 它是由运行期系统调用的, 绝不是应该通过代码调用, 其虽与 load 相似.但是却有几个非常重要的微妙区别,首先, 它是 '惰性调用的', 也就是说, 只有当程序用到了相关的类时, 才会调用, 因此, 如果某各类一直没有使用, 那么其 initilize 方法就一直不会运行. 对于 load 来说, 应用程序必须阻塞并等者所有类的 load 都执行完, 才能继续.
此方法与 load 还有一个区别, 就是运行期系统在执行该方法时, 是处于正常状态的, 因此. 从运行期系统完整度上来讲, 此时可以安全使用并调用任意类中的任意方法, 而且, 运行期系统也能确保 initilize 方法一定会在 '线程安全的环境'中执行,这就是说, 只有执行 initilize 的那个线程可以操作类 或者 类实例,其他线程都要先阻塞, 等着 initilize 执行完.
最后一个区别是: initilize 方法与其他消息一样, 如果某个类未实现它, 而其超类实现了, 那么就会运行超类的实现代码,这听起来并不稀奇, 但却经常为开发者所忽视. 例如: #impoer@interface BaseClass : NSObject
@end
@implementation BaseClass
+ (void)initilize{
NSLog(@" %@ initilize",self);
}
@end
@interface SubClass : BaseClass
@end
@implementation SubClass
@end
即便 SubClass 类没有实现 initilize 方法, 它也会收到这条消息,由各级超类所实现的 initilize 也会先行调用,
与其他方法 (除去load)方法一样, initilize 也遵循通常的继承规则, 所以,当初始化基类 BaseClass 时,BaseClass 中定义的 initilize 方法要运行一遍, 而当初始化其子类 SubClass 时,由于该类并未覆写此方法, 因而还要把 父类的实现代码 再运行一遍,鉴于此, 通常都会这么来实现 initilize 方法.
+ (void)initilize{
if(self == [BaseClass class]){
NSLog(@"%@ initilize",self);
}
}
加上这条检测语句之后, 只有当开发者期望的那个类载入系统时,才会执行相关的初始化操作.
看过load 与 initilize 方法的这些特性之后,又回到早前提过的那个主要问题,也就是这两个方法的实现代码要尽量精简, 在里面设置一些状态, 使本类能够正常运作就可以了, 不要执行那些耗时太久或需要枷锁的任务.对于 load 方法来说, 原因已经解释过了, 对于 initilize 方法,首先初始化可能会阻塞 UI 线程.
其二,开发者无法控制类的初始化时机.
其三, 如果某个类的实现代码很复杂, 那么其中可能会直接或间接用到其他类. 若是其他类尚未初始化, 则系统就会破事其初始化, 然而, 本类的初始化方法此时尚未运行完毕, 其他类在运行其 initiliza 方法时, 有可能会依赖本类的某些数据, 而这些数据此时也许还没有初始化好.
所以, 编写 load 或 initilize 方法时, 一定要留心这些注意事项, 把代码实现的简单一些, 能节省很多的调试时间, 除了初始化全局状态之外, 如果还有其他事情要做, 可以专门创建一个方法来执行这些操作, 并要求该类的使用者必须在使用本类之前调用此方法, 比如说: 单例类 在首次使用之前必须执行一些操作, 可以采用这个方法.
总结:
在加载阶段, 如果类实现了 load 方法,那么系统就会调用它, 分类里也可以定义此方法, 类的 load 方法要比分类中的要先调用, 与其他方法不同 load 方法不参与覆写机制.
首次使用 某个类之前, 系统会向其发送 initilize 消息,由于此方法遵从普通的覆写规则, 所以同城应该在里面判断当前要初始化的是那个类.
load 与 initilize 方法都应该实现的精简一些, 这有助于保持应用程序的响应能力, 也能减少引入 '依赖环'的几率
无法在编译期设定的全局变量, 可以放在 initilize 方法里面初始化.