一门语言的开发入门,总是抬手就能整出一个「Hello World Demo」。比如下面这样:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"Hello, World!");
}
return 0;
}
显然,熟悉 iOS 开发的同学都知道,上面这个来自 Objective-C。今天,我们就从这熟悉的代码入手,来一起研究一下「Hello World」出世的整个过程。
main 函数
众所周知,main 函数是我们程序的入口,我们不妨从此入手,开始我们的表演。
入手的姿势已经确定,甩手一个断点,拿到下图所示的调用堆栈信息:
![加载过程堆栈截图]
显然,在 main 函数执行之前,先是调用了 start 方法。那这个所谓的 start 方法又是什么呢?哪里来的呢?又是怎么调起来的呢?
从上图我们看得并不很真切,因为 + (void)load {} 方法的调用是在加载阶段(后面验证),而加载完之后才触发 main 函数的调用。所以我们在 load 方法上再加上一个断点(这里可以随便弄个类,重写 load 方法),看看究竟。
+load 方法
再次运行代码之后很容易先确认前面提到的「load 方法调用在 main 调用前」,并拿到下面的调用堆栈(bt 命令可打印更详细的堆栈信息):
![load 方法调用堆栈]
从上图我们可以清楚地看到一切的开始源于一堆 dyld 的东西。那么,dyld 是个啥?
dyld(全名 the dynamic link editor)是苹果的动态链接器,用来链接所有的库和可执行文件,是苹果操作系统一个重要组成部分。在系统内核做好程序准备工作之后,交由dyld负责余下的工作。它的代码也是开源的,正常可以从官方仓库获取。
注意:网上有很多关于 dyld 执行流程的介绍,但都是基于稍老的一些版本。可以看到上面的堆栈信息与老版本的也有些许差异,但是总体流程上基本一致。这里介绍基于最新的 dyld4 版本。
我们接着说,在 dyld 做完加载库、可执行文件等一系列准备工作之后,通过 dyld4::RuntimeState::notifyObjCInit 触发 libobjc.A.dylib 中的 load_images 函数,再到我们自定义的 [Person load] 方法的调用,最终到在之后的 main 函数。
此话怎么讲呢?iOS 开发者对 notify 这样的字眼是不是有些熟悉?对的,通知!还是通知的触发。上面的过程很显然就是 dyld 触发通知到注册通知的接收方。哪里呢?libobjc.A.dylib 中的 load_images 函数,这个库其实就是我们常念叨的 Runtime。
Runtime 的代码也是在官方的开源库中,所以我们接下来可以直接验证一下我们的猜想。Runtime 源码是可以运行起来的,当然需要一堆配置,下载下来之后,Target 选择 KCObjcBuild 就可以,源码中直接 Debug,体验非常流畅。
load_images 函数
哦了,接着说,我们怎么验证呢?直接在 Runtime 源码中直接搜索 load_images,很容易定位到下面这里:
![load_images 定义位置]
显然是 load_images 定义的地方,直接甩一断点验证看看(源码直接编译优势凸显):
![load_images 断点验证]
这里调用的堆栈信息,很显然符合我们的猜想。


