Objective-C 中的load和initialize方法
load方法
通过查看NSObject类 可以看到:load方法是NSObject类中的第一个方法
WX20190522-145025.png
通过Apple的官方文档 我们可以看到load方法的特点:
当类被引用进项目的时候就会执行load函数(在main函数开始执行之前),与这个类是否被用到无关,每个类的load函数只会自动调用一次.由于load函数是系统自动加载的,因此不需要调用父类的load函数,否则父类的load函数会多次执行。
- 当父类和子类都实现load函数时,父类的load方法执行顺序要优先于子类
- 当子类未实现load方法时,不会调用父类load方法
- 类中的load方法执行顺序要优先于类别(Category)
- 当有多个类别(Category)都实现了load方法,这几个load方法都会执行,但执行顺序不确定(其执行顺序与类别在Compile Sources中出现的顺序一致)
- 当然当有多个不同的类的时候,每个类load 执行顺序与其在Compile Sources出现的顺序一致
为了验证,我们在工程中新建一个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
根据这个顺序可以得到结论:
- 分类中的load方法总会在本类中的load方法执行完之后再执行
- 分类中的load方法不会被本类的继承关系而影响,是按照Compile Sources中装载的顺序执行的(Student类是Person的子类,但是 [Student(Load) load]方法比[Person(Load) load]先执行)
- 分类的load方法会在所有本类的load方法都执行完之后才执行(虽然Student和Person的category文件是在Animal类前面的,但它们的load方法依然会在Animal的load方法之后执行)
我们再给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会被多次执行。假如这个类放到代码中,而这段代码并没有被执行,这个函数是不会被执行的。
- 父类的initialize方法会比子类先执行
- 当子类未实现initialize方法时,会调用父类initialize方法,子类实现initialize方法时,会覆盖父类initialize方法.
- 当有多个Category都实现了initialize方法,会覆盖类中的方法,只执行一个(会执行Compile Sources 列表中最后一个Category 的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方法的异同:
相同点:
- 方法只会被调用一次
- 内部都使用了锁,是线程安全的
不同点:
- load是只要类所在文件被引用就会被调用,而initialize是在类或者其子类的第一个方法被调用前调用。所以如果类没有被引用到项目中,就不会有load调用;但即使类文件被引用进来,但是没有使用,那么initialize也不会被调用。
- load方法通常用来进行Method Swizzle,initialize方法一般用于初始化全局变量或静态变量。