有时候,类必须先执行某些初始化操作,然后才能正常使用。在OC中,绝大多数类都继承自NSObject这个根类,而该类有两个方法:load方法,initialize方法,可用来实现这种初始化操作。
+ (void) load
1.对于加入运行期系统的类及分类,必定会调用此方法,且仅调用一次。
2.iOS会在应用程序启动的时候调用load方法,在main函数之前调用
3.执行子类的load方法前,会先执行所有超类的load方法,顺序为父类->子类->分类,则先调用类里面的,再调用分类里面的
4.在load方法中使用其他类是不安全的,因为会调用其他类的load方法,而如果关系复杂的话,就无法判断出各个类的载入顺序,类只有初始化完成后,类实例才能进行正常使用load方法的问题在于,执行该方法时,运行期系统处于“脆弱状态”(fragile state)。在执行子类的load方法之前,必定会先执行所有超类的load方法,而如果代码还依赖了其他程序库,那么程序库里相关类的load方法也必定会先执行。然而,根据某个给定的程序库,却无法判断出其中各个类的加载顺序。因此,在load方法中使用其他类是不安全的。比方说,有下面这段代码:
#import <Foundation/Foundation.h>
#import “EOCClassA.h” // From the same library
@interface EOCClassB : NSObject
@end
@implementation EOCClassB
+ (void)load {
NSLog(@“Loading EOCClassB”);
EOCClassA *object = [EOCClassB new];
//Use ‘object’
}
@end
分析:在EOCClassB的load方法里使用EOCClassA却不太安全,因为无法确定在执行EOCClassB的load方法之前,EOCClassA是不是已经加载好了
5.load 方法不遵从继承规则,如果类本身没有实现load方法,那么系统就不会调用,不管父类有没有实现(跟下文的initialize有明显区别)
6.尽可能的精简load方法,因为整个应用程序在执行load方法时会阻塞,即,程序会阻塞直到所有类的load方法执行完毕,才会继续
7.load 方法中最常用的就是方法交换method swizzling
+(void)initialize
1 对于每个类来说,在首次使用该类之前,且仅调用一次.它是由运行期系统来调用的,绝不应该通过代码直接调用.
2 惰性调用,只有当程序使用相关类(该类或子类)时,才会调用,因此,如果某个类一直都没有使用,那么initialize方法就一直不会运行.
3 运行期系统会确保initialize方法是在线程安全的环境中执行,即,只有执行initialize的那个线程可以操作类或类实例。其他线程都要先阻塞,等待initialize执行完
4 如果类未实现initialize方法,而其超类实现了,那么会运行超类的实现代码,而且会运行两次
- initialize 遵循继承规则
- 初始化子类的的时候会先初始化父类,然后会调用父类的initialize方法,而子类没有覆写initialize方法,因此会再次调用父类的实现方法
- 鉴于此,initialize方法实现如下:
+ (void)initialize {
if (self == [People class]) {
NSLog(@"%@ initialize", self);}
}
5 initialize方法也需要尽量精简,只应该用来设置内部数据:
比如,某个全局状态无法在编译期初始化,可以放在initialize里面。
static NSMutableArray *kSomeObjects;
@implementation People
+ (void)initialize {
if (self == [People class]){
kSomeObjects = [NSMutableArray new];
}
}
如果某个类的实现代码很复杂,那么其中可能会直接或间接用到其他类。若那些类尚未初始化,则系统会迫使其初始化。然而,本类的初始化方法此时尚未运行完毕。其他类在运行其initialize方法时,有可能会依赖本类中的某些数据,而这些数据此时也许还未初始化好。例如:
#import <Foundation/Foundation.h>
static id EOCClassAInternalData;
@interface EOCClassA : NOObject
@end
static id EOCClassBInternalData;
@interface EOCClassB : NSObject
@end
@implementation EOCClassA
(void)initialize {
if (self == [EOCClassA class]) {
[EOCClassB doSomethingUsesItsInternalData];
EOCClassAInternalData = [self setupInternalData];}
}
@end
@implementation EOCClassB
+ (void)initialize {
if (self == [EOCClassB class]) {
EOCClassBInternalData = [self setupInternalData];}
}
@end
若是EOCClassA先初始化,那么EOCClassB随后也会初始化,它会在自己的初始化方法中调用EOCClassA的doSomethingThatUsesItsInternalData,而此时EOCClassA内部的数据
还没准备好。在实际编码工作中,问题不能想此处说的那么明显,而且牵涉到的类可能也不止两个。因此,当代码无法正常运行时,想要找出错误就更难了。
整数可以在编译期定义,然而可变数组不行,因为它是个OC对象,所以创建实例只之前必须先激活运行期系统。注意,某些OC对象可以在编译期创建,例如NSString实例。然而,创建下面这种对象会令编译器报错:
static NSMutableArray *kSomeObjects = [NSMutableArray new];
6 对于分类中的initialize方法,会覆盖该类的initialize方法。
要点
- 在加载阶段,如果类实现了load方法,那么系统就会调用它。分类里面也可以定义此方法,类的load方法要比分类中的先调用。与其他方法不同,load方法不参与覆写机制。
- 在首次使用某个类之前,系统会向其发送initialize消息,通常应该在里面判断当前要初始化的类,防止子类未覆写initialize的情况下调用两次
- load与initialize方法都应该实现得精简一些,有助于保持应用程序的响应能力,也能减少引入“依赖环”(interdependency cycle)的几率
- 无法在编译期设定的全局常量,可以放在initialize方法里初始化。