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