我们一般知道 App 程序入口从执行main函数开始,但其实在main函数之前程序做了很多工作。
一、内核启动
APP是由内核引导启动的,kernel内核做好所有准备工作后会得到线程入口及main入口,但是线程不会马上进入main入口,因为还要加载动态链接器(dyld),dyld会将入口点保存下来,等dyld加载完所有动态链接库等工作之后,再开始执行main函数。
初始化空间创建进程等,最重要的是载入动态链接器。
二、动态链接器(dyld)
动态链接库是一组源代码的模块,每个模块包含一些可供应用程序或者其他动态链接库调用的函数,在应用程序调用一个动态链接库里面的函数的时候,操作系统会将动态链接库的文件映像映射到进程的地址空间中,这样进程中所有的线程就可以调用动态链接库中的函数了。
1. 加载动态链接库
dyld是苹果的动态链接器,iOS中系统所有的framework都是动态链接的,在执行我们的代码之前dyld需要加载所有动态库。
使用动态库的好处:
- 代码共用,很多程序都动态链接了这个lib,但它们在内存和磁盘中只有一份。
- 易于维护,由于这些lib是程序执行的时候才去link,所以这些lib很容易去做更新。
- 减少体积,不用像静态库那样被打包进执行文件,可执行文件体积能减少很多。
2. 共享缓存
当构建一个应用程序时,将会链接各种各样的库,它们又会依赖于其他的动态库,需要加载的动态库将会非常多,为了缩短时间动态链接器使用的共享缓存。操作系统中有个单独的文件,这个文件中包含了绝大多数的动态库,这些动态库都已经链接成了一个文件,并且处理好了它们之间的符号关系。当加载一个可执行文件或库时,动态链接器首先会检查共享缓存中是否存在,如果存在,直接拿出来使用。
3. 加载类、方法等符号
简单的说是ImageLoader和dyld、runtime配合将类加载进内存。这里Image我们理解为二进制文件。
整个过程:
- dyld将可执行文件初始化
- 交由ImageLoader加载Image二进制文件(包含了类、方法等各种符号)
- 当新 image 加载进来后交由 runtime 大厨去解析这个二进制文件的符号表和代码
- runtime 接手后调用 map_images做解析和处理,接下来 load_images 中调用 call_load_methods 方法,遍历所有加载进来的 Class,按继承层级依次调用 Class 的 +load 方法和其 Category 的 +load 方法。
至此,可执行文件和动态库中的所有符号(Class、Selector...)都已经成功加载进内存,被runtime所管理,在这之后,runtime的那些方法都可以使用了(动态添加class、方法混合等)。
4. 扩展+load
可以知道,所有 Class 的 load 方法都是在 main 函数之前被调用的,且只会被调用一次。注意,这里调用 load 方法是直接使用函数内存地址 (*load_method)(cls,SEL_load) ,而不是使用消息发送 objc_msgSend 的方式,所以子类中不能调用父类的load方法。
三、main函数
所有初始化工作结束后,终于来到了main函数:
- 执行UIApplicationMain函数
- 创建Application对象,创建ApplicationDelegate,并设为Application的代理。
- 创建RunLoop主循环,代理开始监听事件
- 启动完毕后会执行didFinishLaunching,创建keyWindow,以及weindow的跟视图控制器。
- 显示窗口。
网友评论