Objective-C 中的load和initialize方法

2019-05-22  本文已影响0人  左左4143

load方法

通过查看NSObject类 可以看到:load方法是NSObject类中的第一个方法


WX20190522-145025.png

通过Apple的官方文档 我们可以看到load方法的特点:

当类被引用进项目的时候就会执行load函数(在main函数开始执行之前),与这个类是否被用到无关,每个类的load函数只会自动调用一次.由于load函数是系统自动加载的,因此不需要调用父类的load函数,否则父类的load函数会多次执行。

为了验证,我们在工程中新建一个Person类,继承自NSObject,然后重写它的load方法,但并不去使用这个类

#import "Person.h"

@implementation Person

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

@end

运行程序我们可以看到系统依然执行了load方法里面的内容:

2019-05-22 14:43:41.329890+0800 ZGQTest[971:23304] +[Person load]

我们再创建一个Animal类,也继承自NSObject

#import "Animal.h"

@implementation Animal

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

@end

像上面那样再次运行程序,打印结果为

2019-05-22 15:11:47.529401+0800 ZGQTest[1172:42356] +[Animal load]
2019-05-22 15:11:47.531099+0800 ZGQTest[1172:42356] +[Person load]

此时两个类的load方法都执行了,但是我们是先创建的Person类,后创建的Animal类,为什么会先打印Animal类的load方法呢?

WX20190522-151346.png

原因就在于这两个类在Compile Sources中出现的顺序,我们在工程设置中打开Build Phases 找到Compile Sources,可以看到Animal类是在Person类前面的

WX20190522-151640.png

我们将这两个类的顺序调换,重新运行程序,就会发现这两个类的load方法执行的顺序也发生了改变

2019-05-22 15:36:21.623734+0800 ZGQTest[1273:56530] +[Person load]
2019-05-22 15:36:21.624246+0800 ZGQTest[1273:56530] +[Animal load]

我们可以得出类的load方法执行的顺序是根据类文件在Compile Sources出现的顺序而决定的

此时我们再新建一个Student类,继承自Person,也重写它的load方法,但依然不使用这个类


#import "Person.h"

@interface Student : Person

@end



#import "Student.h"

@implementation Student

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


@end

运行程序,打印结果为

2019-05-22 15:50:34.831903+0800 ZGQTest[1373:66105] +[Person load]
2019-05-22 15:50:34.832777+0800 ZGQTest[1373:66105] +[Animal load]
2019-05-22 15:50:34.832971+0800 ZGQTest[1373:66105] +[Student load]

可以看到Student类的load方法是最后执行的,此时它在Compile Sources中的位置也是排在Person和Animal后面,那么我们手动将Student类拖到这两个类前面,再次运行程序:

[图片上传中...(WX20190522-155318.png-b0a464-1558511609240-0)]

打印结果为:

2019-05-22 15:53:52.584261+0800 ZGQTest[1404:68341] +[Person load]
2019-05-22 15:53:52.585259+0800 ZGQTest[1404:68341] +[Student load]
2019-05-22 15:53:52.585732+0800 ZGQTest[1404:68341] +[Animal load]

可以看到Student类的顺序虽然排到了最前面,但是它的load方法依然会在Person类的load方法之后执行,这就验证了

当父类和子类都实现load函数时,父类的load方法执行顺序要优先于子类

此时我们分别为Person和Student新建两个category,并在category中重写它们的load方法

#import "Person+Load.h"

@implementation Person (Load)

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

@end
#import "Student+Load.h"

@implementation Student (Load)

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

@end

运行结果为:

2019-05-22 16:10:13.026845+0800 ZGQTest[1525:79697] +[Person load]
2019-05-22 16:10:13.027907+0800 ZGQTest[1525:79697] +[Student load]
2019-05-22 16:10:13.028180+0800 ZGQTest[1525:79697] +[Animal load]
2019-05-22 16:10:13.028743+0800 ZGQTest[1525:79697] +[Student(Load) load]
2019-05-22 16:10:13.029152+0800 ZGQTest[1525:79697] +[Person(Load) load]

此时Compile Sources中类的顺序是这样的:


WX20190522-161303.png

根据这个顺序可以得到结论:

我们再给Student类添加一个category,并且重写load方法,运行程序,结果为

2019-05-22 16:26:11.609162+0800 ZGQTest[1647:90389] +[Person load]
2019-05-22 16:26:11.610395+0800 ZGQTest[1647:90389] +[Student load]
2019-05-22 16:26:11.610620+0800 ZGQTest[1647:90389] +[Animal load]
2019-05-22 16:26:11.610928+0800 ZGQTest[1647:90389] +[Student(Load2) load]
2019-05-22 16:26:11.611162+0800 ZGQTest[1647:90389] +[Student(Load) load]
2019-05-22 16:26:11.611570+0800 ZGQTest[1647:90389] +[Person(Load) load]

此时在Compile Sources中Student+Load2.m是在Student+Load.m之前的,所以对于同一个本类的分类来说,它们的load方法执行顺序,是按照Compile Sources中的顺序来决定的。(通过给Person类增加一个load2的category可以看到所有的分类都是根据装载顺序来执行load方法的,不止针对于本类)

注:在本类和多个category中如果有相同的方法,那么当你执行这个方法时只会执行最后一个加载的category中的方法,load方法并没有遵守这个特质

initialize方法

initialize方法 是NSObject中的第二个方法,
通过官方文档我们可以看到initialize的特点:

initialize在类或者其子类的第一个方法被调用前调用。即使类文件被引用进项目,但是没有使用,initialize不会被调用。由于是系统自动调用,也不需要再调用 [super initialize] ,否则父类的initialize会被多次执行。假如这个类放到代码中,而这段代码并没有被执行,这个函数是不会被执行的。

我们将工程中的Person类重写initialize方法,运行程序后发现打印内容和之前一样,只有这几个类的load方法,并没有执行initialize方法

#import "Person.h"

@implementation Person

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

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

@end

然后我们在工程的ViewController文件中,引入Person并初始化一个Person对象

- (void)viewDidLoad {
    [super viewDidLoad]; 
    Person *p = [[Person alloc] init];
}

此时的打印结果为:

2019-05-22 20:07:05.011716+0800 ZGQTest[2196:918218] +[Person initialize]

可以看到Person的initialize方法已经被调用了

此时我们将刚才的Person类换成它的子类Student来执行(注意是替换,也就是说这次的代码中没有用到Person类,而且Student类中还没有重写initialize方法),打印结果为

2019-05-22 20:28:37.797186+0800 ZGQTest[2386:932564] +[Person initialize]
2019-05-22 20:28:37.797309+0800 ZGQTest[2386:932564] +[Person initialize]

可以看到Person的initialize方法,被调用了两次,说明了子类的initialize方法没有实现时,会去调用父类的initialize方法(这一点与load方法不同,当Student类没有实现load方法时,运行结果只有Person类执行了一次load方法,并不是两次,说明子类没有实现load方法时,也不会去调用父类的load方法)

为什么会调用两次,应该是因为创建子类对象时,会先创建父类对象,调用一遍initialize方法,创建子类对象时由于子类没有实现initialize方法,所以再次调用了父类的

接下来我们把 Student类的initialize也重写,运行后:

2019-05-22 20:46:28.761129+0800 ZGQTest[2442:947984] +[Person initialize]
2019-05-22 20:46:28.761253+0800 ZGQTest[2442:947984] +[Student initialize]

此时子类和父类的initialize方法都执行了,此时Student的initialize方法就覆盖了父类的,

如果我们想让某一个类(比如父类Person)的initialize方法只调用一次,可以这样:

+ (void)initialize
{
    if (self == [Person class]) {
        NSLog(@"%s",__func__);
    }
}

此时[Person initialize] 只打印了一次

接下来我们在Person+Load方法中实现initialize方法
打印结果为

2019-05-22 21:07:01.541884+0800 ZGQTest[2660:964029] +[Person(Load) initialize]
2019-05-22 21:07:01.542007+0800 ZGQTest[2660:964029] +[Student initialize]

可以看到分类中的initialize方法会覆盖本类中的initialize方法

我们再在Student+Load方法中实现initialize方法,结果为

2019-05-22 21:08:50.725242+0800 ZGQTest[2685:965488] +[Person(Load) initialize]
2019-05-22 21:08:50.725389+0800 ZGQTest[2685:965488] +[Student(Load) initialize]

此时我们在刚才的所有分类中实现initialize方法,可以看到initialize方法总是会互相覆盖,分类首先会覆盖本类,然后分类之间会根据Compile Sources中出现的顺序进行覆盖(但是跟load方法不同的是,不管分类在Compile Sources中的顺序如何,父类的initialize方法肯定会早于子类的initialize方法执行)

load方法会在main函数之前调用,initialize方法会在main函数之后调用,我们可以在main函数中加一个打印来验证

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

此时的运行结果为

2019-05-22 21:41:53.014826+0800 ZGQTest[3004:985822] +[Person load]
2019-05-22 21:41:53.016625+0800 ZGQTest[3004:985822] +[Animal load]
2019-05-22 21:41:53.016907+0800 ZGQTest[3004:985822] main
2019-05-22 21:41:53.118183+0800 ZGQTest[3004:985822] +[Person initialize]
2019-05-22 21:41:53.118375+0800 ZGQTest[3004:985822] +[Student initialize]

根据官方文档我们可以看粗load方法和initialize方法的异同:

相同点:

不同点:

上一篇下一篇

猜你喜欢

热点阅读