iOS进阶回顾四「load & initialize」
load方法的本质是什么?initialize呢?两个有什么区别?
- 最好的研究方法就是实践:新建两个类实现
load
方法
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface SFPerson : NSObject
@end
NS_ASSUME_NONNULL_END
#import "SFPerson.h"
@implementation SFPerson
+ (void)load{
NSLog(@"SFPerson +load");
}
@end
#import "SFPerson.h"
NS_ASSUME_NONNULL_BEGIN
@interface SFPerson (Helper)
@end
NS_ASSUME_NONNULL_END
#import "SFPerson+Helper.h"
@implementation SFPerson (Helper)
+ (void)load{
NSLog(@"SFPerson (Helper) + load");
}
@end
#import "SFPerson.h"
NS_ASSUME_NONNULL_BEGIN
@interface SFPerson (Addtion)
@end
NS_ASSUME_NONNULL_END
#import "SFPerson+Addtion.h"
@implementation SFPerson (Addtion)
+ (void)load{
NSLog(@"SFPerson (Addtion) + load");
}
@end
2019-08-27 14:16:08.683691+0800 load[33088:238375] SFPerson +load
2019-08-27 14:16:08.684128+0800 load[33088:238375] SFPerson (Helper) + load
2019-08-27 14:38:15.283433+0800 load[33282:256018] SFPerson (Addtion) + load
直接运行后出现上面的打印输出,我们没有导入头文件,也没有进行创建对象调用,系统直接调用load方法由此可见:
分类中也存在load
方法,load
方法是在程序启动时,加载类、分类的时候就会调用。在调用分类的load
方法前会优先调用本类的load
方法。分类中的load
方法谁先编译谁先调用
我们也可以通过源码验证如下:
可以看到类的load方法调用一次就不会调用了
loadable_classes_user>0
这样循环就不会再次执行了继续查看
call_class_loads()
类方法
static void call_class_loads(void)
{
int i;
// Detach current loadable list.
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
直接通过数组的下标得到我们需要的方法
Class cls = classes[i].cls;
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
(*load_method)(cls, SEL_load);
}
// Destroy the detached list.
if (classes) free(classes);
}
和call_category_loads()
分类方法的实现
static bool call_category_loads(void)
{
int i, shift;
bool new_categories_added = NO;
// Detach current loadable list.
struct loadable_category *cats = loadable_categories;
int used = loadable_categories_used;
int allocated = loadable_categories_allocated;
loadable_categories = nil;
loadable_categories_allocated = 0;
loadable_categories_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Category cat = cats[i].cat;
load_method_t load_method = (load_method_t)cats[i].method;
Class cls;
if (!cat) continue;
cls = _category_getClass(cat);
if (cls && cls->isLoadable()) {
if (PrintLoading) {
_objc_inform("LOAD: +[%s(%s) load]\n",
cls->nameForLogging(),
_category_getName(cat));
}
(*load_method)(cls, SEL_load);
cats[i].cat = nil;
}
}
// Compact detached list (order-preserving)
shift = 0;
for (i = 0; i < used; i++) {
if (cats[i].cat) {
cats[i-shift] = cats[i];
} else {
shift++;
}
}
used -= shift;
// Copy any new +load candidates from the new list to the detached list.
new_categories_added = (loadable_categories_used > 0);
for (i = 0; i < loadable_categories_used; i++) {
if (used == allocated) {
allocated = allocated*2 + 16;
cats = (struct loadable_category *)
realloc(cats, allocated *
sizeof(struct loadable_category));
}
cats[used++] = loadable_categories[i];
}
// Destroy the new list.
if (loadable_categories) free(loadable_categories);
// Reattach the (now augmented) detached list.
// But if there's nothing left to load, destroy the list.
if (used) {
loadable_categories = cats;
loadable_categories_used = used;
loadable_categories_allocated = allocated;
} else {
if (cats) free(cats);
loadable_categories = nil;
loadable_categories_used = 0;
loadable_categories_allocated = 0;
}
if (PrintLoading) {
if (loadable_categories_used != 0) {
_objc_inform("LOAD: %d categories still waiting for +load\n",
loadable_categories_used);
}
}
return new_categories_added;
}
-
我们看到无论是分类还是类都是通过找到下标直接调用的没有通过
msgSend消息转发
,所以load
方法的本质调用为:load根据函数地址直接调用,在runtime加载类分类的时候调用,只会调用一次,先调用类的load方法再去调用分类的load,先编译的类,先调用,调用子类的load之前,先调用父类的load方法
-
我们再添加一个
SFStudent
类继承SFPerson
分别实现一个类方法run
方法
#import "SFPerson.h"
@implementation SFPerson
+ (void)load{
NSLog(@"SFPerson +load");
}
+(void)run{
NSLog(@"SFPerson +run");
}
@end
利用[SFStudent run];
进行调用输入如下:
2019-08-27 14:45:34.859386+0800 load[33449:263656] SFPerson +load
2019-08-27 14:45:34.859677+0800 load[33449:263656] SFPerson (Helper) + load
2019-08-27 14:45:34.859693+0800 load[33449:263656] SFPerson (Addtion) + load
2019-08-27 14:45:34.859769+0800 load[33449:263656] SFPerson (Addtion) +run
由此可知:分类中重写类方法时,分类的类方法会优先调用。我们利用SFStudent
调用run
方法时,输出为SFPerson (Addtion) +run
优先调用的原因
下面继续initialize
分析:
- 我们在
SFPerson
中添加+(void)initialize
方法
#import "SFPerson.h"
@implementation SFPerson
+(void)initialize{
NSLog(@" SFPerson initialize ");
}
@end
在main.m
中调用如下
#import <Foundation/Foundation.h>
#import "SFStudent.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
[SFStudent run];
}
return 0;
}
2019-08-27 15:39:29.802479+0800 load[39941:335527] SFPerson initialize
2019-08-27 15:39:29.802491+0800 load[39941:335527] SFPerson initialize
- 我们看到调用了两次
initialize
方法,为什么呢?
#import <Foundation/Foundation.h>
#import "SFStudent.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
[SFStudent run];
[SFStudent run];
[SFStudent run];
}
return 0;
}
2019-08-27 16:20:53.860266+0800 load[40134:360148] SFPerson initialize
2019-08-27 16:20:53.860282+0800 load[40134:360148] SFPerson initialize
-
多次调用的输入结果和上面的一样
initialize方法会在类第一次接收消息的时候调用 调用顺序:先调用父类的+initialize,在调用子类的+initialize
无论多少次调用都执行一次,是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)
查看源码:
9FF98C1983E5B2CA36457C94BE87F727.png
查看源码可以看到initialize方法是通过消息转发机制实现的
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}
调用时间:
load
方法是可以继承的,但是一般情况下,都是不会主动调用load
方法,都是让系统自动调用+ initialize
方法会在类第一次接收消息的时候调用
调用顺序:- 先调用父类的
+initialize
,在调用子类+initialize
父类的初始化方法可能调用多次, 先初始化父类再初始化子类,而且每个类只会初始化一次, 如果分类实现了+initialize
,就覆盖类本身的+initialize
方法- 先调用类的
load
方法再去调用分类的load
,先编译的类,先调用,调用子类的load
之前,先调用父类的load
调用方式的:+initialize
是通过objc_msgSend
进行调用的load
根据函数地址直接调用,在runtime
加载类分类的时候调用,只会调用一次