iOS---Objective-C: +load vs +initialize
在 NSObject 类中有两个非常特殊的类方法 +load 和 +initialize ,用于类的初始化。这两个看似非常简单的类方法在许多方面会让人感到困惑,比如:
- 子类、父类、分类中的相应方法什么时候会被调用?
- 需不需要在子类的实现中显式地调用父类的实现?
- 每个方法到底会被调用多少次?
下面,我们将结合 runtime(我下载的是当前的最新版本 objc4-646.tar.gz) 的源码,一起来揭开它们的神秘面纱。
+load
+load 方法是当类或分类被添加到 Objective-C runtime 时被调用的,实现这个方法可以让我们在类加载的时候执行一些类相关的行为。子类的 +load 方法会在它的所有父类的 +load 方法之后执行,而分类的 +load 方法会在它的主类的 +load 方法之后执行。但是不同的类之间的 +load 方法的调用顺序是不确定的。
打开 runtime 工程,我们接下来看看与 +load 方法相关的几个关键函数。首先是文件 objc-runtime-new.mm 中的 void prepare_load_methods(header_info *hi) 函数:
1 |
|
顾名思义,这个函数的作用就是提前准备好满足 +load 方法调用条件的类和分类,以供接下来的调用。其中,在处理类时,调用了同文件中的另外一个函数 static void schedule_class_load(Class cls) 来执行具体的操作。
1 |
|
其中,函数第 9 行代码对入参的父类进行了递归调用,以确保父类优先的顺序。void prepare_load_methods(header_info *hi) 函数执行完后,当前所有满足 +load 方法调用条件的类和分类就被分别存放在全局变量 loadable_classes 和 loadable_categories 中了。
准备好类和分类后,接下来就是对它们的 +load 方法进行调用了。打开文件 objc-loadmethod.m ,找到其中的 void call_load_methods(void) 函数。
1 |
|
同样的,这个函数的作用就是调用上一步准备好的类和分类中的 +load 方法,并且确保类优先于分类的顺序。我们继续查看在这个函数中调用的另外两个关键函数 static void call_class_loads(void) 和 static BOOL call_category_loads(void) 。由于这两个函数的作用大同小异,下面就以篇幅较小的 static void call_class_loads(void) 函数为例进行探讨。
1 |
|
这个函数的作用就是真正负责调用类的 +load 方法了。它从全局变量 loadable_classes 中取出所有可供调用的类,并进行清零操作。
1 |
|
其中 loadable_classes 指向用于保存类信息的内存的首地址,loadable_classes_allocated 标识已分配的内存空间大小,loadable_classes_used 则标识已使用的内存空间大小。
然后,循环调用所有类的 +load 方法。注意,这里是(调用分类的 +load 方法也是如此)直接使用函数内存地址的方式 (*load_method)(cls, SEL_load); 对 +load 方法进行调用的,而不是使用发送消息 objc_msgSend 的方式。
这样的调用方式就使得 +load 方法拥有了一个非常有趣的特性,那就是子类、父类和分类中的 +load 方法的实现是被区别对待的。也就是说如果子类没有实现 +load 方法,那么当它被加载时 runtime 是不会去调用父类的 +load 方法的。同理,当一个类和它的分类都实现了 +load 方法时,两个方法都会被调用。因此,我们常常可以利用这个特性做一些“邪恶”的事情,比如说方法混淆(Method Swizzling)。
+initialize
+initialize 方法是在类或它的子类收到第一条消息之前被调用的,这里所指的消息包括实例方法和类方法的调用。也就是说 +initialize 方法是以懒加载的方式被调用的,如果程序一直没有给某个类或它的子类发送消息,那么这个类的 +initialize 方法是永远不会被调用的。那这样设计有什么好处呢?好处是显而易见的,那就是节省系统资源,避免浪费。
同样的,我们还是结合 runtime 的源码来加深对 +initialize 方法的理解。打开文件 objc-runtime-new.mm ,找到以下函数:
1 |
|
当我们给某个类发送消息时,runtime 会调用这个函数在类中查找相应方法的实现或进行消息转发。从第 8-14 的关键代码我们可以看出,当类没有初始化时 runtime 会调用 void _class_initialize(Class cls) 函数对该类进行初始化。
1 |
|
其中,第 7-12 行代码对入参的父类进行了递归调用,以确保父类优先于子类初始化。另外,最关键的是第 36 行代码(暴露了 +initialize 方法的本质),runtime 使用了发送消息 objc_msgSend 的方式对 +initialize 方法进行调用。也就是说 +initialize 方法的调用与普通方法的调用是一样的,走的都是发送消息的流程。换言之,如果子类没有实现 +initialize 方法,那么继承自父类的实现会被调用;如果一个类的分类实现了 +initialize 方法,那么就会对这个类中的实现造成覆盖。
因此,如果一个子类没有实现 +initialize 方法,那么父类的实现是会被执行多次的。有时候,这可能是你想要的;但如果我们想确保自己的 +initialize 方法只执行一次,避免多次执行可能带来的副作用时,我们可以使用下面的代码来实现:
1 |
|
总结
通过阅读 runtime 的源码,我们知道了 +load 和 +initialize 方法实现的细节,明白了它们的调用机制和各自的特点。下面我们绘制一张表格,以更加直观的方式来巩固我们对它们的理解:
| +load | +initialize | |
|---|---|---|
| 调用时机 | 被添加到 runtime 时 | 收到第一条消息前,可能永远不调用 |
| 调用顺序 | 父类->子类->分类 | 父类->子类 |
| 调用次数 | 1次 | 多次 |
| 是否需要显式调用父类实现 | 否 | 否 |
| 是否沿用父类的实现 | 否 | 是 |
| 分类中的实现 | 类和分类都执行 | 覆盖类中的方法,只执行分类的实现 |
参考链接
- https://www.mikeash.com/pyblog/friday-qa-2009-05-22-objective-c-class-loading-and-initialization.html
- http://blog.iderzheng.com/objective-c-load-vs-initialize/
- http://nshipster.com/method-swizzling/
iOS---Objective-C: +load vs +initialize的更多相关文章
- Objective C类方法load和initialize的区别
Objective C类方法load和initialize的区别 过去两个星期里,为了完成一个工作,接触到了NSObject中非常特别的两个类方法(Class Method).它们的特别之处,在于 ...
- iOS load和initialize的区别
可能有些还不清楚load和initialize的区别,下面简单说一下: 首先说一下 + initialize 方法:苹果官方对这个方法有这样的一段描述:这个方法会在 第一次初始化这个类之前 被调用,我 ...
- Objective c, +load, +initialize 方法
+load() 当类被加载入程序的时候会执行+load方法 +initialize() 当类第一次被使用的时候会执行+initialize方法 这两个方法都只会被执行一次.
- load与initialize
NSObject类有两种初始化方式load和initialize load + (void)load; 对于加入运行期系统的类及分类,必定会调用此方法,且仅调用一次. iOS会在应用程序启动的时候调用 ...
- NSObject的load和initialize方法(转)
全文转载自:http://www.cocoachina.com/ios/20150104/10826.html 在Objective-C中,NSObject是根类,而NSObject.h的头文件中前两 ...
- oc---类方法load和initialize的区别
在iOS开发中,就像Application有生命周期回调方法一样,在Objective-C的类被加载和初始化的时候,也可以收到方法回调,可以在适当的情况下做一些定制处理.而这正是本篇文章所要介绍的lo ...
- +load 和 +initialize
APP 启动到执行 main 函数之前,程序就执行了很多代码. 执行顺序: 将程序依赖的动态链接库加载到内存 加载可执行文件中的所有符号,代码 runtime 解析被编译的符号代码 遍历所有的 cla ...
- load和initialize方法
一.load 方法什么时候调用: 在main方法还没执行的时候 就会 加载所有类,调用所有类的load方法. load方法是线程安全的,它使用了锁,我们应该避免线程阻塞在load方法. 在项目中使 ...
- OC load与initialize
OC load与initialize load 当类被引用进程序的时候会执行这个函数 一个类的load方法不用写明[super load],父类就会收到调用,并且在子类之前. Category的loa ...
- load 与initialize的调用顺序小结
开发中实用方法固然是最贴近应用的,当一些程序原理还是要先搞清晰,根据查找的一些资料,总结了一些load与initialize的调用. APP启动到执行main函数之前,程序就执行了很多代码 执行顺 ...
随机推荐
- Java环境变量搭建(Linux环境)
1. 下载解压JDK压缩包 例如:解压到 /opt/jdk1.7.0_80 下 2. 添加环境变量到 /etc/profile 文件中 vi /etc/profile 在文件末尾追加如下内容: exp ...
- 关于请求时状态为cancel
项目中发现有一个问题,在我发送某些请求的时候请求一会状态就变为cancel了,我滴个乖乖,这是咋回事,被取消了,后来经过仔细排查后发现了以下两个问题 1.AJAX和form表单同时使用,(form提交 ...
- for循环和数组练习
//公鸡2文,母鸡1文,小鸡半文,每种至少一只,100文买100只鸡有多少种可能 var ci =0; for(var g=1;g<50;g++){ for(var m=1;m<100;m ...
- android设备局域网中快速搜索之cling方式
cling方式就像pc端windows局域网工作组刷新显示一样,原来用过扫描ip地址的方式,可以使用就是有点慢,还有一种自己加入组广播,通过发送组广播的方式. android设备局域网中快速搜索之 ...
- git常用命令以及速查命令
工作中使用的是git,所以写这个只是为了加深自己的记忆,提高熟练度 共勉~ git 主要命令 要关联一个远程库,使用命令git remote add origin git@server-name:pa ...
- Linux磁盘I/O性能监控——iostat
iostat命令可以查看CPU利用率和磁盘性能相关数据,有时候我们会觉得系统响应慢,传数据很慢,这个慢可能是多方面原因导致的,如CPU利用率高.网络差.系统平均负载高甚至是磁盘已经损坏了.对此,系统性 ...
- C++ ADL
即在一个名称作为调用运算符的左操作数时,并且这个名字是一个无限定名称时,在无限定查找到的名字集合中额外增加的一个规则使集合范围扩大(从而可以定位到其他一些限定名称),通常是用来保证定义在不同命名空间的 ...
- LeetCode946-验证栈序列
问题:验证栈序列 给定 pushed 和 popped 两个序列,只有当它们可能是在最初空栈上进行的推入 push 和弹出 pop 操作序列的结果时,返回 true:否则,返回 false . 示例 ...
- Python 正则表达式 匹配次数
管道可以匹配多个正则表达式中的一个 >>> >>> m=re.search(r'Batman|Tina Fey','Batman and Tina Fey')> ...
- MySQL存储引擎MyISAM与InnoDB的区别比较
使用MySQL当然会接触到MySQL的存储引擎,在新建数据库和新建数据表的时候都会看到. MySQL默认的存储引擎是MyISAM,其他常用的就是InnoDB了. 至于到底用哪种存储引擎比较好?这个问题 ...