深入详解 iOS的 +load和+initialize

在这之前,我从没有想过,+load和+initialize能扯出这么多东西来,但今天确实扯出这么多,如有错误之处,欢迎指正哈~~~

  • +load 方法是系统自动调用的,无需手动调用,系统自动为每一个类调用+load方法(如果有),所以也无需手动调用[super load]方法。
  • +load 方法按照[SuperClass load]->[Class load]->[ChildClass load]的顺序加载。
  • +load 方法是在所有类被加入到runtime以后调用的。
  • [ChildClass load]方法是按照Compile Sources的排列顺序加载的,但要遵循调用[ChildClass load]之前,必须先调用其[SuperClass load]方法。
  • 在所有类的+load方法调用完以后再调用[Category load]方法,[Category load]的调用顺序完全按照Compile Sources排列顺序。

为了方便阅读,我将console中的输出时间全部去掉了,

学习的开始,首先我们新建工程一个LoadAndInitializeTest项目

一、+load 方法是系统自动调用的,无需手动调用,系统自动为每一个类调用+load方法(如果有),所以也无需手动调用[super load]方法。

  1. 在Xcode中新建文件夹(object),然后新建一个NSObject的子类MyObject。
  2. 在MyObject的.m文件中加入以下代码,然后run
+(void)load {
    NSLog(@"%s",__FUNCTION__);
}

打印输出

LoadAndInitializeTest[27822:1740708] +[MyObject load]

我们并没有手动调用MyObject的任何方法,但是+load方法确实调用了,所以+load 方法是系统自动调用的,无需手动调用。

  1. 在Xcode中新建文件夹(super),然后新建一个NSObject的子类MyObjectSuper,
    然后将MyObject的父类改成MyObjectSuper。
    并在MyObjectSuper.m中输入以下代码,然后run
+(void)load {
    NSLog(@"%s",__FUNCTION__);
}

打印输出

LoadAndInitializeTest[31059:1753828] +[MyObjectSuper load]
LoadAndInitializeTest[31059:1753828] +[MyObject load]

可见,父类的 +load方法也是自动加载的,无需手动调用。

  1. 在[MyObject load]中添加 [super load],然后run
    打印输出
LoadAndInitializeTest[33168:1761400] +[MyObjectSuper load]
LoadAndInitializeTest[33168:1761400] +[MyObjectSuper load]
LoadAndInitializeTest[33168:1761400] +[MyObject load]

可见 [MyObjectSuper load] 被调用了两次,也说明了[SuperClass load]方法也是自动加载的,无需手动调用。
为了安全起见,在+load中一定要做唯一性判断,一般使用以下代码。

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
            doSomething
    });
}
  1. 我们再多做一个测试,如果某个程序员在[ChildClass load]中,手贱写一个[super load],而[SuperClass load]的职责是利用黑魔法进行方法交换,[SuperClass load]就会调用两次,方法交换了两次,就等于没有交换了,如果不懂+load方法的使用,像这个的bug,我们却很难发现。

在父类(MyObjectSuper)中添加以下代码
在此只是为了演示+load方法,关于黑魔法的坑就不在此详解了

#import "MyObjectSuper.h"
#import <objc/runtime.h>

@implementation MyObjectSuper

+ (void)load{
    NSLog(@"%s",__FUNCTION__);
    Method method1 = class_getInstanceMethod([self class], NSSelectorFromString(@"dealloc"));
    Method method2 = class_getInstanceMethod([self class], @selector(deallocSwizzle));
    method_exchangeImplementations(method1, method2);
}

-(void)dealloc {
    NSLog(@"%s",__FUNCTION__);
}

- (void)deallocSwizzle{
     NSLog(@"%s",__FUNCTION__);
    [self deallocSwizzle];
}

@end

在子类(MyObject)中添加以下代码,
注意这一行代码:[super load];

#import "MyObject.h"

@implementation MyObject

+ (void)load {
    [super load];
    NSLog(@"%s",__FUNCTION__);
}
@end

我们在其他地方创建并销毁对象,然后run

- (void)viewDidLoad {
    [super viewDidLoad];
    [[MyObject alloc] init];
}

打印输出

LoadAndInitializeTest[74856:1942900] +[MyObjectSuper load]
LoadAndInitializeTest[74856:1942900] +[MyObjectSuper load]
LoadAndInitializeTest[74856:1942900] +[MyObject load]
LoadAndInitializeTest[74856:1942900] -[MyObjectSuper dealloc]

分析结果:[MyObjectSuper load]被调用了两次,方法被交换了两次,等于没有交换,所以对象销毁时,调用[MyObjectSuper dealloc],而没有调用[MyObjectSuper deallocSwizzle]。

那我们现在把子类里的[super load];注释掉,其他代码不做修改,然后run
打印输出

LoadAndInitializeTest[76655:1950334] +[MyObjectSuper load]
LoadAndInitializeTest[76655:1950334] +[MyObject load]
LoadAndInitializeTest[76655:1950334] -[MyObjectSuper deallocSwizzle]
LoadAndInitializeTest[76655:1950334] -[MyObjectSuper dealloc]

哈哈~~~,这样就正常了,
我们一定要知道,+load方法可能会被调用多次,在load方法中,我们必须做判断,因为总有一个程序员会继承你的类,然后在load方法中调用[super load]。

  1. 现在我们在父类中的修改代码如下
+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"%s",__FUNCTION__);
        Method method1 = class_getInstanceMethod([self class], NSSelectorFromString(@"dealloc"));
        Method method2 = class_getInstanceMethod([self class], @selector(deallocSwizzle));
        method_exchangeImplementations(method1, method2);
    });
}

子类代码如下,然后run

+ (void)load {
   [super load];
   NSLog(@"%s",__FUNCTION__);
}

打印输出

LoadAndInitializeTest[76655:1950334] +[MyObjectSuper load]
LoadAndInitializeTest[76655:1950334] +[MyObject load]
LoadAndInitializeTest[76655:1950334] -[MyObjectSuper deallocSwizzle]
LoadAndInitializeTest[76655:1950334] -[MyObjectSuper dealloc]

这下是真的正常了,我们再也不怕子类+load方法中被调用[super load]了

  1. 我在阅读一些知名的第三库时,如AFNetworking、以及小码哥的MJRefresh时,确实发现一些不严谨的做法,虽然并不是严重的bug,但是总归是隐患,代码如下
AFNetworking 代码,只贴一小部分了
@implementation _AFURLSessionTaskSwizzling
+ (void)load {
    if (NSClassFromString(@"NSURLSessionTask")) {
        我创建了一个_AFURLSessionTaskSwizzling的子类,
        在子类中重新+load方法,然后[super load];
        发现程序确实能调到这里
    }
}
小码哥的  MJRefresh 的load
@implementation UIViewController (Example)
+ (void)load
{
    Method method1 = class_getInstanceMethod([self class], NSSelectorFromString(@"dealloc"));
    Method method2 = class_getInstanceMethod([self class], @selector(deallocSwizzle));
    method_exchangeImplementations(method1, method2);
}

在滴滴出行客户端里是这样写的,可见我大滴滴早就注意到这个问题了

@implementation BaseViewController (categroy)
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //方法交换
    });
}

二、+load 方法按照[SuperClass load]->[Class load]->[ChildClass load]->顺序加载的。

  1. MyObjectSuper和MyObject注释掉无用信息,只保留以下代码
+ (void)load {
    NSLog(@"%s",__FUNCTION__);
}
@end

我们新建child文件夹,并在child文件夹下创建MyObject的子类MyObjectChild,添加如下代码,然后run

#import "MyObjectChild.h"
@implementation MyObjectChild
+ (void)load {
    NSLog(@"%s",__FUNCTION__);
}
@end

打印输出

LoadAndInitializeTest[9937:2079123] +[MyObjectSuper load]
LoadAndInitializeTest[9937:2079123] +[MyObject load]
LoadAndInitializeTest[9937:2079123] +[MyObjectChild load]

可见+load方法的调用顺序是从父类开始,然后子类,再子类,
我尝试一下更改Compile Sources 里的顺序,结果依然如此,证明了+load方法的调用顺序是从父类顺序到子类的。

三、+load 方法是在所有类被加入到runtime以后调用的。

在分享这个问题之前,我们先来看一小段Apple关于+load的文档

Invoked whenever a class or category is added to the Objective-C runtime; implement this method to perform class-specific behavior upon loading.

对于此段文档,大神朱晓辉是这么翻译的

当类(Class)或者类别(Category)加入Runtime中时(就是被引用的时候)。实现该方法,可以在加载时做一些类特有的操作。

而我是这么翻译的

当类或者类别加入到Runtime中以后,实现该方法可以在加载时做一些类特有的操作。

以上两段翻译根本不同点是“时”和“以后”,我认为“时”,是正在进行时,是正在添加。而“以后”是Add操作成功以后的事,是一种完成时态。
现在我们就来测一下,到底是怎么回事!

修改MyObject.h如下
遵守了一个NSObject的协议,添加了两个property属性

@interface MyObject : MyObjectSuper <NSObject>
@property (nonatomic, copy)   NSString *name;
@property (nonatomic, assign) NSInteger age;
@end

修改 MyObject.m 如下,有点多,大家自己看吧

#import <objc/runtime.h>

@implementation MyObject {
    int nIval;//第一处增加
}

+ (void)load {
    NSLog(@"%s",__FUNCTION__);
    
    //第二处增加
    NSLog(@"-1.------华丽的风格下-------");
    unsigned int count = 0;
    Class metaClass = object_getClass([MyObject class]);
    Method *methods = class_copyMethodList(metaClass, &count);
    for (int i = 0; i < count; i++) {
        NSLog(@"类方法为:%s", sel_getName(method_getName(methods[i])));
    }
    free(methods);

    NSLog(@"-2.------华丽的风格下------");
    unsigned int countMethod = 0;
    methods = class_copyMethodList([self class], &countMethod);
    for (int i = 0; i < countMethod; i++) {
        NSLog(@"实例方法为:%s", sel_getName(method_getName(methods[i])));
    }
    free(methods);

    NSLog(@"-3.------华丽的风格下-------");
    unsigned int countIval = 0;
    Ivar *ivals = class_copyIvarList([self class], &countIval);
    for (int i = 0; i < countIval; i++) {
        NSLog(@"变量为:%s", ivar_getName(ivals[i]));
    }
    free(ivals);
    
    NSLog(@"-4.------华丽的风格下------");
    unsigned int countProperty = 0;
    objc_property_t *propertys = class_copyPropertyList([self class], &countProperty);
    for (int i = 0; i < countProperty; i++) {
        NSLog(@"属性为:%s", property_getName(propertys[i]));
    }
    free(propertys);
    
    NSLog(@"-5.------华丽的风格下------");
    unsigned int countProtocol = 0;
    __unsafe_unretained Protocol **protocols = class_copyProtocolList([self class], &countProtocol);
    for (int i = 0; i < countProtocol; i++) {
        NSLog(@"协议为:%s", protocol_getName(protocols[i]));
    }
    NSLog(@"------华丽的风格下------");

    MyObject *myObject = [[MyObject alloc] init];
    myObject.age = 18;
    myObject.name = @"司晓刚";
    NSLog(@"myObject.name=%@,myObject.age=%ld",myObject.name, myObject.age);
}

- (void)objectFunction {//第三处增加
    NSLog(@"%s",__FUNCTION__);
}

+ (void)classFunction {//第四处增加
    NSLog(@"%s",__FUNCTION__);
}
@end

现在你可以多想一想,你心中认为的和真实输出是否一致
打印输出

LoadAndInitializeTest[33804:2175226] +[MyObjectSuper load]
LoadAndInitializeTest[33804:2175226] +[MyObject load]
LoadAndInitializeTest[33804:2175226] -1.------华丽的风格下------
LoadAndInitializeTest[33804:2175226] 类方法为:classFunction
LoadAndInitializeTest[33804:2175226] 类方法为:load
LoadAndInitializeTest[33804:2175226] -2.-------华丽的风格下-----
LoadAndInitializeTest[33804:2175226] 实例方法为:objectFunction
LoadAndInitializeTest[33804:2175226] 实例方法为:.cxx_destruct
LoadAndInitializeTest[33804:2175226] 实例方法为:name
LoadAndInitializeTest[33804:2175226] 实例方法为:setName:
LoadAndInitializeTest[33804:2175226] 实例方法为:setAge:
LoadAndInitializeTest[33804:2175226] 实例方法为:age
LoadAndInitializeTest[33804:2175226] -3.-------华丽的风格下-----
LoadAndInitializeTest[33804:2175226] 变量为:nIval
LoadAndInitializeTest[33804:2175226] 变量为:_name
LoadAndInitializeTest[33804:2175226] 变量为:_age
LoadAndInitializeTest[33804:2175226] -4.-------华丽的风格下-----
LoadAndInitializeTest[33804:2175226] 属性为:name
LoadAndInitializeTest[33804:2175226] 属性为:age
LoadAndInitializeTest[33804:2175226] 属性为:hash
LoadAndInitializeTest[33804:2175226] 属性为:superclass
LoadAndInitializeTest[33804:2175226] 属性为:description
LoadAndInitializeTest[33804:2175226] 属性为:debugDescription
LoadAndInitializeTest[33804:2175226] -5.-------华丽的风格下-----
LoadAndInitializeTest[33804:2175226] 协议为:NSObject
LoadAndInitializeTest[33804:2175226] ------华丽的风格下---------
LoadAndInitializeTest[33804:2175226] myObject.name=司晓刚,myObject.age=18
LoadAndInitializeTest[33804:2175226] +[MyObjectChild load]

前两条和最后一条+load方法,我们都能看明白了,直接忽视就好。

第一条华丽的分割线打印的是类方法,
我们成功的打印出classFunction、load。

第二条华丽的分割线打印的是实例方法,
objectFunction首先被打印出来,
“.cxx_destruct”是什么,自己去研究吧,
属性name和属性age的set、get方法被打印出来。

第三条华丽的分割线打印的是变量,
nIval是我们自己直接定义的,
_name和_age是属性name和age自动为我们生成的带有下划线的变量。

第四条华丽的分割线打印的是属性
name和age就不用解释了
superclass、description、debugDescription也不解释了,自己研究吧。

第五条华丽的分割线打印的是协议
NSObject 协议是我在头文件中添加的。

到这儿,最重要的一点开始了,
我实例化了一个MyObject对象,并且给他赋值,然后成功的打印出来了,
这就说明,我们这个类以及完全可以正常使用了,难道这还不能说明类的+load方法是在类加载完成以后才调用的吗?如果正在加载的话,我们能完整的得到类的这么多信息和使用类吗?

很显然,我认为我是对的,如有错误之处,欢迎指正

让我们重新回到Apple的那段文档,开始下一个问题

Invoked whenever a class or category is added to the Objective-C runtime; implement this method to perform class-specific behavior upon loading.

Apple说,当一个类或者分类被加入到runtime以后被调用,我以前一直认为当把一个类加入到runtime以后,立刻调用它的+load方法,然后再去加载它的兄弟类或者子类,也就是说,我在+load方法中,去获取它子类的信息或者实例化子类,都不会成功的,因为类还没有加入到runtime中。

子类上面已经说过了,它在父类的+load后加载

修改MyObjectChild.h 如下

@interface MyObjectChild : MyObject <NSObject>
@property (nonatomic, copy)   NSString *nameChild;
@property (nonatomic, assign) NSInteger ageChild;
@end

修改MyObjectChild.m 如下

@implementation MyObjectChild{
    int nIvalChild;
}

+ (void)load {
    NSLog(@"%s",__FUNCTION__);
}

-(void)objectFunctionChild {
    NSLog(@"%s",__FUNCTION__);
}

+ (void)classFunctionChild {
    NSLog(@"%s",__FUNCTION__);
}
@end

在[MyObject load]最后添加以下代码

    NSLog(@"------以下是子类华丽的分割线------");
    
    
    NSLog(@"-1.------华丽的风格下Child------");
    MyObjectChild *myObjectChild = [[MyObjectChild alloc] init];
    myObjectChild.age = 18;
    myObjectChild.name = @"司晓刚";
    NSLog(@"myObjectChild.name=%@,myObjectChild.age=%ld",myObjectChild.name, myObjectChild.age);
    
    count = 0;
    metaClass = object_getClass([MyObjectChild class]);
    methods = class_copyMethodList(metaClass, &count);
    for (int i = 0; i < count; i++) {
        NSLog(@"Child类方法为:%s", sel_getName(method_getName(methods[i])));
    }
    free(methods);
    
    NSLog(@"-2.------华丽的风格下Child------");
    countMethod = 0;
    methods = class_copyMethodList([myObjectChild class], &countMethod);
    for (int i = 0; i < countMethod; i++) {
        NSLog(@"Child实例方法为:%s", sel_getName(method_getName(methods[i])));
    }
    free(methods);
    
    NSLog(@"-3.------华丽的风格下Child-------");
    countIval = 0;
    ivals = class_copyIvarList([myObjectChild class], &countIval);
    for (int i = 0; i < countIval; i++) {
        NSLog(@"Child变量为:%s", ivar_getName(ivals[i]));
    }
    free(ivals);
    
    NSLog(@"-4.------华丽的风格下Child------");
    countProperty = 0;
    propertys = class_copyPropertyList([myObjectChild class], &countProperty);
    for (int i = 0; i < countProperty; i++) {
        NSLog(@"Child属性为:%s", property_getName(propertys[i]));
    }
    free(propertys);
    
    NSLog(@"-5.------华丽的风格下Child-------");
    countProtocol = 0;
    protocols = class_copyProtocolList([myObjectChild class], &countProtocol);
    for (int i = 0; i < countProtocol; i++) {
        NSLog(@"Child协议为:%s", protocol_getName(protocols[i]));
    }
    NSLog(@"------华丽的风格下Child------");

输出打印

LoadAndInitializeTest[55040:2254257] ------以下是子类华丽的分割线------
LoadAndInitializeTest[55040:2254257] -1.------华丽的风格下Child------
LoadAndInitializeTest[55040:2254257] myObjectChild.name=司晓刚,myObjectChild.age=18
LoadAndInitializeTest[55040:2254257] Child类方法为:classFunctionChild
LoadAndInitializeTest[55040:2254257] Child类方法为:load
LoadAndInitializeTest[55040:2254257] -2.------华丽的风格下Child-------
LoadAndInitializeTest[55040:2254257] Child实例方法为:objectFunctionChild
LoadAndInitializeTest[55040:2254257] Child实例方法为:nameChild
LoadAndInitializeTest[55040:2254257] Child实例方法为:setNameChild:
LoadAndInitializeTest[55040:2254257] Child实例方法为:ageChild
LoadAndInitializeTest[55040:2254257] Child实例方法为:setAgeChild:
LoadAndInitializeTest[55040:2254257] Child实例方法为:.cxx_destruct
LoadAndInitializeTest[55040:2254257] -3.------华丽的风格下Child-------
LoadAndInitializeTest[55040:2254257] Child变量为:nIvalChild
LoadAndInitializeTest[55040:2254257] Child变量为:_nameChild
LoadAndInitializeTest[55040:2254257] Child变量为:_ageChild
LoadAndInitializeTest[55040:2254257] -4.------华丽的风格下Child------
LoadAndInitializeTest[55040:2254257] Child属性为:nameChild
LoadAndInitializeTest[55040:2254257] Child属性为:ageChild
LoadAndInitializeTest[55040:2254257] Child属性为:hash
LoadAndInitializeTest[55040:2254257] Child属性为:superclass
LoadAndInitializeTest[55040:2254257] Child属性为:description
LoadAndInitializeTest[55040:2254257] Child属性为:debugDescription
LoadAndInitializeTest[55040:2254257] -5.------华丽的风格下Child-------
LoadAndInitializeTest[55040:2254257] Child协议为:NSObject

这些输出,我就不解释了,输出证明了,在父类的+load中已经能获取到子类的信息并且可以实例化子类了。

我证明的是把所有的类都加入到runtime以后,然后开始调用+load方法,而不是Apple说的一个类,对于这一点,挑战性过大,请大神指正。

四、+load方法是按照Compile Sources的排列顺序加载的,但要遵循调用[ChildClass load]之前,必须先调用其[SuperClass load]方法。

  1. 在object文件夹中创建两个MyObjectSuper的子类MyObject1和MyObject2,
    在child文件夹中创建一个MyObject的子类MyObjectChild1
    并且在三个.m里实现以下代码,然后run
+ (void)load {
    NSLog(@"%s",__FUNCTION__);
}

输出打印

LoadAndInitializeTest[75083:2339602] +[MyObjectSuper load]
LoadAndInitializeTest[75083:2339602] +[MyObject load]
LoadAndInitializeTest[75083:2339602] +[MyObject1 load]
LoadAndInitializeTest[75083:2339602] +[MyObject2 load]
LoadAndInitializeTest[75083:2339602] +[MyObjectChild load]
LoadAndInitializeTest[75083:2339602] +[MyObjectChild1 load]

我们现在去查看以下Compile Sources,并且截图如下


屏幕快照 2018-11-24 下午11.18.26.png

我们发现Compile Sources里的顺序竟然与我们打印的顺序惊人的一致,难道真的是这样吗?
我们随意拖动Compile Sources的排列顺序,然后run


屏幕快照 2018-11-24 下午11.28.24.png

打印输出

LoadAndInitializeTest[78440:2353132] +[MyObjectSuper load]
LoadAndInitializeTest[78440:2353132] +[MyObject load]
LoadAndInitializeTest[78440:2353132] +[MyObjectChild1 load]
LoadAndInitializeTest[78440:2353132] +[MyObjectChild load]
LoadAndInitializeTest[78440:2353132] +[MyObject2 load]
LoadAndInitializeTest[78440:2353132] +[MyObject1 load]
  • Compile Sources 里的第一个类是MyObjectChild1,
  • 在调用[MyObjectChild1 load]之前会先调用其父类[MyObject load],
  • 在调用[MyObject load]之前会调用其父类[MyObjectSuper load],
    所以,Compile Sources 里第一个类就打印出来前三个+load方法,
LoadAndInitializeTest[78440:2353132] +[MyObjectSuper load]
LoadAndInitializeTest[78440:2353132] +[MyObject load]
LoadAndInitializeTest[78440:2353132] +[MyObjectChild1 load]
  • Compile Sources 里的第二个类是MyObjectChild,
  • 在调用[MyObjectChild load]之前会先调用其父类[MyObject load],因为父类的+load方法已经被调用,所以无需再调用。
  • 在调用[MyObject load]之前会调用其父类[MyObjectSuper load],因为父类的+load方法已经被调用,所以无需再调用。
    所以,Compile Sources 里第二个类就打印出来第四个+load方法,
LoadAndInitializeTest[78440:2353132] +[MyObjectChild load]
  1. [MyObject2 load]和[MyObject1 load]完全跟以上一致原理,请自行推理。

事实上,Apple的文档是这样写的

A class’s +load method is called after all of its superclasses’ +load methods.
一个类的+load方法在调用前,会先调用其父类的+load。

我们实验得出的结论与Apple的文档是一致的,如果Apple文档再加上,类的+load方法按照Compile Sources里顺序调用的,两条规则合并起来就完美了。

五、在所有类的+load方法调用完以后再去调用[Category load]方法,[Category load]的调用顺序完全按照Compile Sources排列顺序。

我们现在创建一系列的分类,如下,并分别实现其+load方法,然后run
@interface MyObjectSuper (superCategory0)
@interface MyObjectSuper (superCategory1)

@interface MyObject (category0)
@interface MyObject (category1)
@interface MyObject1 (category0)
@interface MyObject1 (category1)
@interface MyObject2 (category0)
@interface MyObject2 (category1)

@interface MyObjectChild (category0)
@interface MyObjectChild (category1)
@interface MyObjectChild1 (category0)
@interface MyObjectChild1 (category1)

打印输出

LoadAndInitializeTest[90277:2399103] +[MyObjectSuper load]
LoadAndInitializeTest[90277:2399103] +[MyObject load]
LoadAndInitializeTest[90277:2399103] +[MyObjectChild1 load]
LoadAndInitializeTest[90277:2399103] +[MyObjectChild load]
LoadAndInitializeTest[90277:2399103] +[MyObject2 load]
LoadAndInitializeTest[90277:2399103] +[MyObject1 load]
LoadAndInitializeTest[90277:2399103] +[MyObjectSuper(superCategory0) load]
LoadAndInitializeTest[90277:2399103] +[MyObject(category1) load]
LoadAndInitializeTest[90277:2399103] +[MyObject(category0) load]
LoadAndInitializeTest[90277:2399103] +[MyObject2(category1) load]
LoadAndInitializeTest[90277:2399103] +[MyObject2(category0) load]
LoadAndInitializeTest[90277:2399103] +[MyObjectChild(category1) load]
LoadAndInitializeTest[90277:2399103] +[MyObjectChild1(category0) load]
LoadAndInitializeTest[90277:2399103] +[MyObject1(category0) load]
LoadAndInitializeTest[90277:2399103] +[MyObjectChild(category0) load]
LoadAndInitializeTest[90277:2399103] +[MyObject1(category1) load]
LoadAndInitializeTest[90277:2399103] +[MyObjectChild1(category1) load]
LoadAndInitializeTest[90277:2399103] +[MyObjectSuper(superCategory1) load]

我们发现,类的+load方法全部调用完以后才会调用[category load]方法。
我们现在去修改category文件在Compile Sources里的顺序,我们会很容易发现,Compile Sources里的顺序与我们输出的顺序,总是完全一致。

Apple的文档上是这么写的

A category +load method is called after the class’s own +load method.
一个Category的+load方法在其所属类的+load方法之后调用

苹果的这段文档,我不能说他不对,但是我得到的结论是,[category load]的调用是在所有类的+load之后,而不是特定的类(the class)之后。
如有错误,欢迎指正。

我们再做一个有趣的测试
我们修改main.m文件如下,然后 run

int main(int argc, char * argv[]) {
    @autoreleasepool {
        NSLog(@"main");
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

输出打印

...
LoadAndInitializeTest[95236:2418416] +[MyObjectChild(category0) load]
LoadAndInitializeTest[95236:2418416] +[MyObject1(category1) load]
LoadAndInitializeTest[95236:2418416] +[MyObjectChild1(category1) load]
LoadAndInitializeTest[95236:2418416] +[MyObjectSuper(superCategory0) load]
LoadAndInitializeTest[95236:2418416] +[MyObjectSuper(superCategory1) load]
LoadAndInitializeTest[95236:2418416] main

main 竟然是最后输出的,说明了所有的load方法都是先于main函数被调用的。

现在我对+load的使用进行总结:

一、+load 方法是在所有类被加入到runtime以后,main函数执行之前被系统自动调用的。

二、系统自动为每一个类调用+load方法(如果有),无需手动调用,也无需手动调用[super load]。

三、+load方法会按照文件所在的Compile Sources顺序加载,在调用类的+load之前,会优先调用其父类的+load方法。

四、在所有类的+load方法调用完以后再调用[Category load]方法,加载顺序按照Compile Sources排列顺序。

+initialize:

我们在程序中有很多类似于以下的代码,我们称为懒加载,
首先

@property (nonatomic, strong) UILabel *myLabel;

然后

- (UILabel *)myLabel {
    if (!_myLabel) {
        _myLabel = [[UILabel alloc] init];
        ...
    }
    return _myLabel;
}

最后

[self addSubview:self.myLabel];

这种懒加载的方式是,直到第一次向myLabel发送消息时,才会创建myLabel对象。
+initialize方法也是类似的原理,在类第一次接收消息时被调用。

事实上,Apple的文档是这么写的,也就是说,他总在用户调用之前调用。

Initializes the class before it receives its first message.
在这个类接收第一条消息之前调用。

关于+initialize方法,我总结如下

一、+ initialize 在类第一次接收到消息之前被系统自动调用,无需手动调用。

二、在调用子类的+ initialize 方法之前,会先调用父类的+ initialize 方法(如果有),所以也无需手动调用[super initialize]方法。

三、如果父类中有+ initialize方法,而子类中没有+ initialize方法,子类会自动继承父类的+ initialize方法,也就是说父类的+ initialize方法会调用两次。

四、Category中+ initialize方法会覆盖类中的+ initialize,同一个类有多个Category都实现了+initialize方法时,Compile Sources 列表中最后一个Category 的+initialize方法会覆盖其他的+ initialize方法。

一、+ initialize 在类第一次接收到消息之前被系统自动调用,无需手动调用。

  1. 在MyObjectChild里添加如下代码,然后run
- (instancetype)init {
    NSLog(@"init");
    if (self = [super init]) {
    }
    return self;
}

+ (void)initialize {
    NSLog(@"%s",__FUNCTION__);
}

然后无任何+ initialize输出

我们现在在Controller中添加如下代码,然后run

#import "MyObjectChild.h"

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"[[MyObjectChild alloc] init]; 之前");
    [[MyObjectChild alloc] init];
    NSLog(@"[[MyObjectChild alloc] init]; 之后");
}

输出打印

LoadAndInitializeTest[98430:2830873] [[MyObjectChild alloc] init]; 之前
LoadAndInitializeTest[98430:2830873] +[MyObjectChild initialize]
LoadAndInitializeTest[98430:2830873] init
LoadAndInitializeTest[98430:2830873] [[MyObjectChild alloc] init]; 之后

+initialize 打印信息被输出了,这就说明了+ initialize 在类第一次接收到消息之前被系统自动调用,无需手动调用。

二、在调用子类的+ initialize 方法之前,会先调用父类的+ initialize 方法(如果有),所以也无需手动调用[super initialize]方法。

在MyObject、MyObjectSuper分别加入以下代码,然后run

+ (void)initialize {
    NSLog(@"%s",__FUNCTION__);
}

打印输出

LoadAndInitializeTest[30644:2557942] +[MyObjectSuper initialize]
LoadAndInitializeTest[30644:2557942] +[MyObject initialize]
LoadAndInitializeTest[30644:2557942] +[MyObjectChild initialize]

从父类到子类依次被打印出来,说明+ initialize与+load方法一样,在调用子类的方法时,会先调用父类的方法。
现在我们在MyObjectChild里加入以下代码,然后run

[super initialize];

打印输出

LoadAndInitializeTest[33679:2569542] +[MyObjectSuper initialize]
LoadAndInitializeTest[33679:2569542] +[MyObject initialize]
LoadAndInitializeTest[33679:2569542] +[MyObject initialize]
LoadAndInitializeTest[33679:2569542] +[MyObjectChild initialize]

这说明子类中无需手动调用[super initialize]方法。

三、如果父类实现了+ initialize方法,而子类没有实现+ initialize,子类会自动继承父类的+ initialize,也就是说,父类的+initialize方法,会被自动调用两次,

现在我们注释掉MyObjectChild、MyObject 的+initialize,然后run
打印输出

LoadAndInitializeTest[35163:2575853] +[MyObjectSuper initialize]
LoadAndInitializeTest[35163:2575853] +[MyObjectSuper initialize]
LoadAndInitializeTest[35163:2575853] +[MyObjectSuper initialize]

我的天啊,[MyObjectSuper initialize]竟然被打印了三次,
因为MyObject会继承父类的+ initialize方法,
而MyObjectChild也会继承父类的+ initialize方法,
所以他们都继承了MyObjectSuper的+ initialize方法,所以打印了三次。

在这我特别说明一点,+ initialize从名字上看,是初始化函数,我们就会认为只调用一次,而且其他很多博客里都明确说明+ initialize只调用一次,但事实上,他确实会自动调用多次,如果我这有错误之处,还希望能给指正。

因为+ initialize方法会被自动继承,所以,+ initialize的出错率要比+load更大一些。

那+initialize方法里到底该怎么写,我知道的通常有两种办法,
第一种

+ (void)initialize{
    if (self == [MyClass class]) {
          ....
    }
}

第二种

+ (void)initialize {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
            ...
    });
}

四、Category中+ initialize方法会覆盖类中的+ initialize,同一个类有多个Category都实现了+initialize方法时,Compile Sources 列表中最后一个Category 的+initialize方法会覆盖其他的+ initialize方法。

  1. 在@implementation MyObjectChild (category0)中添加以下代码,然后run
+ (void)initialize {
    NSLog(@"%s",__FUNCTION__);
}

输出打印

LoadAndInitializeTest[64871:2690309] +[MyObjectSuper initialize]
LoadAndInitializeTest[64871:2690309] +[MyObject initialize]
LoadAndInitializeTest[64871:2690309] +[MyObjectChild(category0) initialize]

category0 里的+ initialize覆盖了类里的+ initialize。

  1. 在@implementation MyObjectChild (category1)中添加以下代码,然后run
+ (void)initialize {
    NSLog(@"%s",__FUNCTION__);
}

输出打印

LoadAndInitializeTest[66414:2697021] +[MyObjectSuper initialize]
LoadAndInitializeTest[66414:2697021] +[MyObject initialize]
LoadAndInitializeTest[66414:2697021] +[MyObjectChild(category1) initialize]

category1 里的+ initialize又覆盖了category0里的+ initialize。

我们去Compile Sources中查看一下,此时MyObjectChild category1肯定排在category0的后面,我们也可以随意更改排列顺序,Compile Sources中最后一个category肯定覆盖其他所有的+ initialize方法。

我们也可以去修改其父类的category方法,发现父类也同样遵守这样的规则。

好吧,我这就写完了,+ initialize的总结与+ initialize开始总结的一模一样,不重复总结了。

这第一次写技术文章,难免有不足之处,如果有错误之处,还请指正。

如果你已经能看到这里,说明你已经足够有耐心了,
关于+load,我估计丢掉一个知识点,你知道是什么吗?

本文所有代码已经上传至GitHub

写本文时,我重点参考了以下两篇博客
类方法load和initialize的区别
iOS类方法load和initialize详解

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容