欢迎大家继续阅读iOS原理探索系列篇章(后续陆续完善补充)
前世今生
说到iOS底层原理,相信很多开发童鞋脑子里闪过很多东西,runtime,KVC,KVO,Runloop等等一系列的词汇,但是很多人没有完全融汇贯通成为一套体系,我自己也会有这样的烦恼,我们如何去整合这些原理,由浅入深,由易到难的把这些知识形成知识树,所以才有了这个系列,用来梳理我自己的技能树。
破冰之始
说到技能树,那就不得不从最基础的东西开始延伸,所以我一开始想到的东西就是程序启动流程,对于iOS开发来说,我们的任何程序都是从main函数开始启动的,那么我们就先从程序的main函数开始分析,看看程序启动之前系统到底做了哪些事情。
main函数解析
这里我新建了一个全新的工程,并且运行起来,在main函数处打断点,如下图:
从上图可以看到,程序在
main函数执行之前有start函数的执行,我们再看看start,如下,可以看到程序在进入main函数之前,先执行了libdyld.dylib库的start函数,再看可能大家就和我一样有些懵逼了,都是一些汇编代码,没有办法去继续看具体的实现了,所以为了清楚的查看具体的细节,接下来给大家介绍一下几个调试方式。
断点调试黑科技
很多时候我们都会遇到上面的情况,很多系统方法,或者类库,底层实现都没有办法去查看具体细节,这里我在断点调试的地方添加了三个符号断点_objc_init,libdispatch_init,libSystem_initializer,结果如下图:
(关于符号断点问题,请查看我的下一篇iOS原理探索系列之alloc&init原理探索里面说明了三种断点调试方法)
添加过符号断点之后,我们再看启动调试细节如下图:
我们可以看到程序在最开始的时候
- 先启动
dyld,然后分别执行_dyld_start、dyldbootstrap::start、dyld::_main; - 然后到
dyld_sim的start_sim、dyld::_main、开始加载ImageLoader; - 接着加载
libSystem库的libSystem_initializer; - 再接下来就是
libdispatch库的libdispatch_init、_os_object_init; - 最后来到
libobjc库的_objc_init完成初始化;
整个流程可清晰看到dyld执行到runtime初始化之前的调用了。
程序启动流程分析
根据上面的断点调试细节,我们可以初步总结App启动main()函数执行前的流程:
- 程序先启动
dyld库,通过dyld配置运行环境,加载共享内存、可执行文件(Mach-O)、动态库、全局C++对象的构造函数的调用、一系列的初始化(runtime,+load等)、dyld注册回调函数 libSystem的初始化libSystem_initializerlibdispatch_init队列环境的准备- 从
_os_object_init过渡到_objc_init,以及_dyld_objc_notify_register镜像文件的映射 - 类-分类-属性-协议-SEL-方法 的加载
- 展开分析
Runtime各个部分的原理 - 启动
main函数
这里只是根据断点初步分析,后续会详细分析每个具体流程底层实现。
备注
关于dyld
dyld(the dynamic link editor)是苹果的动态链接器,在系统内核做好程序准备工作之后,交由dyld负责余下的工作,在App启动时它就负责加载mach-o文件。 它代码是开源的,源码地址。 关于 dyld详细解析说明,可参考dyld详解。
关于Mach-O
Mach-O文件为Mach Object文件格式的缩写,它是一种用于可执行文件,目标代码,动态库,内核转储的文件格式。
常见的有以下形式:
Executable可执行文件Dylib动态库和Framework动态库,对应头文件和资源文件集合
Apple可执行文件格式几乎都是Mach-O;
关于更多Mach-O ,可参考Mac OS X ABI Mach-O File Format Reference(带中文翻译);
关于Mach-O数据结构,可参考Mac本地路径下的/usr/include/mach-o源码;
为了直观看出Mach-O相关信息,可以使用三方工具MachOView;
编译好的工程很老了,建议下载源码自己运行使用;
关于安装源码启动报错,可参看别人已经写好的说明,我这边就不多说了;