OC中load和initialize的区别

OC文件在编译后,类相关的数据结构会保留在目标文件中,在运行时得到解析和使用。在应用程序运行起来的时候,类的信息会有加载和初始化过程,这个过程就涉及到了类的两个类方法:loadinitialize。下面我们就来介绍一下这2个方法的区别。(首先要说明一下,这2个方法是系统调用的,开发者一般不会主动去调用者两个方法,这么做也没有什么意义,所以后面的讲解都是针对系统调用,不考虑主动调用的情况)。

1. load方法

1.1 调用时机

当我们启动程序时,参与了编译的类、分类都会被加载进内存,load方法就是在这个类被加载的时候调用的(前提是这个类有实现load方法),这个过程与这个类是否被使用是无关的,也就是说如果有一个类(MyClass)即使在整个程序中都没有用到,甚至没有任何一个文件去引入MyClass的头文件,MyClass的的load的方法一样会被调用。等所有的类、分类都加载进内存后才会调用程序的main函数,所以所有类的load方法都是在main函数之前被调用的。而且每个类、分类的load方法只会被调用一次。

1.2 调用顺序

一个程序中如果所有的类、分类都实现了load方法,那么所有的load方法都会被调用。它们的执行顺序遵循以下规则:

  • 先执行所有类的load方法,再执行所有分类的load方法。
  • 执行类的load方法时,是按照参与编译的顺序,先编译的类先执行,但是如果某个类是继承自另一个类,那么会先执行父类的load方法个再执行自己的load方法。
  • 执行分类的load方法时,是按照分类参与编译的顺序,先编译的分类先执行。

关于编译顺序,我们可以在项目的Build Phases --> Compile Sources查看,最上面的就最先编译,我们可以拖动文件来调整编译顺序。

下面举个例子来看下load方法的执行顺序。首先说明一下几个类的关系:Person类有aaabbb两个分类,men类继承自Person类,men也有2个分类cccdddBook类和前面这些类没有任何关系。

2.jpg

解释:

  • 编译顺序从上到下,上面先编译,下面后编译。由于先执行类的load再执行分类的load,最先参与编译的类是men,而men继承自Person,所以最先执行Personload(虽然Person是后参与编译的,但是它是父类,所以会先执行),然后再执行menload。接着参与编译的是Book类,所以紧接着就是执行Bookload。再接着参与编译的类就是Person,由于它的load方法已经执行过了,此时就不会执行了。
  • 所有的类的load方法都执行完后开始执行分类的load,分类参与编译的顺序是men+ccc-->Person+aaa-->men+ddd-->Person+bbb,所以分类的load方法个也是按照这个顺序执行。

1.3 执行方式

我们知道,当分类中存在和本类中同名的方法时,调用这个方法最终执行的是分类中的方法。那上面就很奇怪了,PersonPerson的分类中都有load方法,按理说调用load方法时最终只会调用其中一个分类的load方法,可结果Person本类和它的2个分类都调用了load方法。

这是因为load方法和普通方法调用的方式不一样。普通方法调用是通过消息发送机制实现的,会先去类或元类的方法列表中查找,如果找到了方法就执行,如果没有找到就去父类的方法列表里面找,只要找到就会终止查找,所以只会执行一次。

load方法调用时,每个类都是根据load方法的地址直接调用,而不会走objc_msgSend函数的方法查找流程,也就是说一个类有实现load方法就执行,没有就不执行(没有的话也不会去父类里面查找)。

想要了解更加详细的底层实现流程,可以去看objc4源码,这里提供一下相关函数调用流程以便进行源码阅读:
首先从objc-os.mm文件的_objc_init函数开始-->load_images-->prepare_load_methods-->schedule_class_load-->add_class_to_loadable_list-->add_category_to_loadable_list-->call_load_methods-->call_class_loads-->call_category_loads-->(*load_method)(cls, SEL_load)

1.4 实现load方法时要注意什么

我们通常在load方法中进行方法交换(Method Swizzle),除此之外,除非真的有必要,我们尽量不要在load方法中写代码,尤其不要在load方法中使用其它的类,因为这个时候其它的类可能还没有被加载进内存,随意使用可能会出问题。

如果确实要在load方法写一些代码,那也要尽量精简代码,不要做一些耗时或者等待锁的操作,因为整个程序在执行load方法时都会阻塞,从而导致程序启动时间过长甚至无法启动。

2. initialize方法

2.1 调用时机

initialize方法是在类或它的子类收到第一条消息时被调用的,这里的消息就是指实例方法或类方法的调用,所以所有类的initialize调用是在执行main函数之后调用的。而且一个类只会调用一次initialize方法。如果一个类在程序运行过程中一直没有被使用过,那这个类的initialize方法也就不会被调用,这一点和load方法是不一样的。

2.2 调用方式

initialize方法的调用和普通方法调用一样,也是走的objc_msgSend流程。所以如果一个类和它的分类都实现了initialize方法,那最终调用的会是分类中的方法。

如果子类和父类都实现了initialize方法,那么会先调用父类的方法,然后调用子类的方法个(这里注意子类中不需要写[super initialize]来调用父类的方法,通过查看源码得知它是在底层实现过程中主动调用的父类的initialize方法)。

下面看一个例子:

父类Person实现了initializePersonSub1PersonSub2这两个子类也实现了initializePersonSub3PersonSub4这两个子类没有实现了initialize,按照下面的顺序实例化对象:

PersonSub1 *ps1 = [[PersonSub1 alloc] init];
Person *person = [[Person alloc] init];
PersonSub2 *ps2 = [[PersonSub2 alloc] init];
PersonSub3 *ps3 = [[PersonSub3 alloc] init];
PersonSub4 *ps4 = [[PersonSub4 alloc] init];

// ***************打印结果***************
2020-01-06 15:52:38.429218+0800 CommandLine[68706:7207027] +[Person initialize]
2020-01-06 15:52:38.429250+0800 CommandLine[68706:7207027] +[PersonSub1 initialize]
2020-01-06 15:52:38.429287+0800 CommandLine[68706:7207027] +[PersonSub2 initialize]
2020-01-06 15:52:38.429347+0800 CommandLine[68706:7207027] +[Person initialize]
2020-01-06 15:52:38.429380+0800 CommandLine[68706:7207027] +[Person initialize]

看到这个运行结果,有人就有疑问了:不是说一个类只会调用一次initialize方法吗,为什么这里Personinitialize方法被调用了3次?

这里就需要讲解一下底层源码的执行流程了,每个类都有一个标记记录这个类是否调用过initialize,我这里就用一个BOOL类型的isInitialized来表示,然后用selfClass来表示自己的类,用superClass来表示父类,下面我用伪代码来描述一下底层源码执行流程:

// 如果自己没有调用过initialize就执行里面的代码
if(!selfClass.isInitialized){ 
    if(!superClass.isInitialized){
        // 如果父类没有执行过initialize就给父类发消息(一旦成功执行initialize就将父类的isInitialized置为YES)
        objc_msgSend(superClass,@selector(initialize));
    }
    // 再给自己的类发消息(一旦成功执行initialize就将自己类的isInitialized置为YES)
    objc_msgSend(selfClass,@selector(initialize));
}

接下来我来按照这个流程来解释一下上面运行的结果:

  • 首先PersonSub1被使用,而此时PersonSub1isInitialized为NO,而且父类PersonisInitialized也为NO,所以先给父类发消息执行initialize,执行完后PersonisInitialized变为YES。然后PersonSub1执行自己的initialize,执行完后PersonSub1isInitialized变为YES。所以这一步先打印+[Person initialize],然后打印+[PersonSub1 initialize]
  • 然后是Person实例化,此时PersonisInitialized为YES,所以不会再调用initialize。所以这一步什么都没打印。
  • 接着是PersonSub2实例化,此时PersonSub2isInitialized为NO,父类PersonisInitialized为YES,所以只有PersonSub2会执行initialize,执行完后PersonSub2isInitialized变为YES。所以这一步打印的是+[PersonSub2 initialize]
  • 再接着是PersonSub3实例化,此时PersonSub3isInitialized为NO,父类PersonisInitialized为YES,所以只有PersonSub3会执行initialize,但是由于PersonSub3没有实现initialize,它就会去父类找这个方法的实现,找到后就执行父类Personinitialize(注意这里是PersonSub3执行的Person中的initialize,而不是Person执行的),执行完后PersonSub3isInitialized变为YES。所以这一步打印的是+[Person initialize]。(注意这里打印的是方法信息,表示执行的是Person中的initialize,而不是说是Person调用的initialize)。
  • 最后是PersonSub4实例化,这一步过程和上面一步是一样的,执行完后PersonSub4isInitialized变为YES。这一步打印的是+[Person initialize]

所以最后的结果就是PersonPersonSub1PersonSub2PersonSub3PersonSub4这5个类都执行了一次initialize,虽然从运行结果来看Personinitialize执行了3次,其实后面2次是PersonSub3PersonSub4调用的。

2.3 使用注意事项

虽然使用initialize要比使用load安全(因为在调用initialize时所有类已经被加载进内存了),但我们还是要尽量少用initialize这个方法个,尤其要谨慎在分类中实现initialize方法,因为如果在分类中实现了,本类实现的initialize方法将不会被调用。实际开发中initialize方法一般用于初始化全局变量或静态变量。

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

推荐阅读更多精彩内容