学习笔记:Objective-C load 与 initialize 方法

简介

在前东家的工作项目中,遇到过使用 +load 进行 Method Swizzling 相关配置的情况,当时大概查了下 +load 方法的特点和用法,也没有太在意;后来看资料发现还有个 +initialize 方法,与 +load 有相似之处但区别也很大,于是趁当下有闲暇时间,系统阅览文档后写下学习笔记,以作日后回顾参考。

共同点

看过官方文档以及别人的博客后觉得,它们共同点不大,若是从使用角度去看,可以认为都可以用作类的初始化设定。但由于两者的调用次数和调用时机乃至调用时的外部环境(如其他类是否已初始化)都有区别,因此使用场景需加以区分。

调用时序

+load

+load 是应用开始运行,对应类(class)或者类别(category)被加载到 runtime 时调用,而且早在 main 函数被调用前。而且在每次加载,不同类的 +load 调用顺序是不确定的,因此方法里的代码不能依赖于某个其他类的 +load 调用。除此之外,其特点可以归结为以下:

  • 只被调用一次。
  • 如果项目中有父类,子类以及类别,并且每个文件中都实现了 +load,其调用顺序为:父类 -> 子类 -> 父类类别 -> 子类类别
  • 如果父类实现了 +load 而子类没有实现,父类的 +load 也会被调用,因为子类被 runtime 加载理所当然地需要父类被加载。因此我们不需要也不应该调用 [super load]

+initialize

懒加载是 +initialize+load 很大的一个区别。它的懒是在该类在应用中第一次接收消息前(如第一次调用 [Class alloc])才被调用。如果该类在项目中没使用过,就永远不会被调用。其特点归结如下:

  • 可能会被调用不止一次。会被多次调用的情况有两种:
    1. 子类没有实现 +initialize,同时在项目中子类第一次接收消息比父类的早时;
    2. 子类调用 [super initialize]

为了避免方法内代码被多次调用,苹果文档建议检查方法调用时类的类型:

+ (void)initialize {
  if (self == [ClassName self]) {
    // ... do the initialization ...
  }
}
  • 调用顺序:父类 -> 子类
  • 线程安全。例如父类在线程A处在 +initialize 方法执行中,子类在线程B触发父类的 +initialize 时需要等待线程A的执行完才会执行。因为会导致线程阻塞,方法里不应该执行复杂耗时的操作。

例子

通过代码例子能跟直观地了解二者的特性与调用时序:

// headers
// 父类
@interface Animal : NSObject
@end

// 子类
@interface Bird : NSObject
@end

// 父类类别
@interface Animal (Talkable)
@end

// 子类类别
@interface Bird (Talkable)
@end

//********** 分隔线 **********//

// implementation
// 父类
@implementation Animal
+ (void)load
{
    NSLog(@"Animal load called");
}

+ (void)initialize
{
    NSLog(@"Animal initialize called");
}
@end

// 子类
@implementation Bird
+ (void)load
{
    NSLog(@"Bird load called");
}

+ (void)initialize
{
    NSLog(@"Bird initialize called");
}
@end

// 父类类别
@implementation Animal (Talkable)
+ (void)load
{
    NSLog(@"Animal category load called");
}
@end

// 子类类别
@implementation Bird (Talkable)
+ (void)load
{
    NSLog(@"Bird category load called");
}
@end

然后在项目某个地方调用,例如在 application:didFinishLaunchingWithOptions:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    Bird *bird = [[Bird alloc] init];
    Animal *animal = [[Animal alloc] init];
    
    return YES;
}

运行应用后控制台输出:

Animal load called
Bird load called
Animal category load called
Bird category load called
Animal initialize called
Bird initialize called

要验证其他情况,在子类调用 super 以及调整下对象创建顺序即可,这里不再赘述。

小结

两者对比之下,+load 的使用场景比 +initialize 更加广泛。因为只被调用一次的特性,而且调用时机早,+load 很适合进行 Method Swizzling 的操作。详细源代码实现,可在参考中查阅。

参考

  • load
  • initialize
  • 你真的了解 Objective-C 中的load 方法么?
  • Objective-C中懒惰的 initialize 方法

你可能感兴趣的:(学习笔记:Objective-C load 与 initialize 方法)