OC底层-dyld应用生命周期(上)
2023-11-22 16:07:03
作为开发人员的我们,通常会认为一个app一旦从0开始开发,它的生命周期是:
- 一个app从头到尾开发
- 发布版本
- app被用户使用
- app被删除
而事实上,app的各个生命周期的背后,其实都有系统在把控。
这篇文章就侧重探索了:
- 用户点击app到app的main函数之前到底发生了什么?
- 在运行时之前又发生了什么?
- 用户的main函数执行结束意味着app的生命周期的结束吗?
- dyld与app的运行时又有什么关系?
0. 术语说明
-
dyld :
动态链接器(dyld)是一个用于实现动态链接的动态链接器。动态链接是链接过程的延迟,该过程使得程序可以等检查其他可执行文件或库文件来确定某些符号的位置和值后才将其修改到可执行代码中。 dyld 是 Mach-O 运行时环境一部分,它提供了运行时代码进行动态链接的机制。 -
runtime :
运行时是执行某些操作时所需的那些环境和条件。runtime需要提供一种执行环境,以便计算机程序可以安全地运行并且运行时不应该被程序修改。 -
Mach-O :
Mach-O是用于实现可执行文件和目标代码文件的格式规范的名称。 Mach-O文件格式允许使用Objective-C或Objective-C++开发的程序使用完整的动态链接。
1. app 的生命周期
1.1 理论中的app生命周期
一个app的生命周期我们认为是:
- 开发人员开发一个app,开发完后需要给这个app创建一个bundle。这个bundle包括可执行文件、应用源码文件和资源文件。
- bundle被Apple Store发布后,用户开始下载和安装。
- 终端用户点击app,app开始运行,app的runtime开始和操作系统进行交互。
- app运行一段时间后,用户点击退出,app的生命周期结束。
1.2 实际上app的生命周期
步骤1 : 终端用户点击app,这个过程会先触发一个叫launch的process。这个process专门用于app的运行环境准备。
步骤2 : 准备就绪后,就会执行一个专用于UIThread(UI主线程)的runloop。
步骤3 : 执行完专用于UIThread(UI主线程)的runloop后,此时才会执行main函数。
步骤4 : 用户点击退出app时,runtime会首先向app的main线程发出一个退出指令。收到退出的指令,app的runtime向每个线程发出退出指令。
步骤5 : app生命周期结束。
2. main函数之前launch过程
在app的生命周期中,我们总会在app的bundle中添加一个可执行文件。这个可执行文件就会在app点击后由mach创建一个新的process,这个process一般就被成为launch process,在这个process中会首先创建专门用于UI的runloop,这个runloop主要用于监听用户交互,比如用户点击、用户触摸等。
3. runtime之前主要发生了什么?
3.1创建线程和runloop
Mach的进程除了通常的main线程外,还存在其他线程,分别用于处理事件、音频、显示等。Mach将这些线程分配到CPU内核执行,不同的任务有不同的服务质量等级(QoS),这些等级由操作提供。每个runloop与一个线程关联,用于处理消息队列。
3.2 runloop
基本原理
每个线程都有一个runloop,runloop就是线程不断循环处理事件的机制。runloop的机制使线程可以一直处理事件,直至没有需要处理的事件时才挂起。每个事件都关联着事件处理函数,事件在运行时通过message进行派发,message的类型由消息派发器预先设定。Mach在应用程序中创建了许多runloop,当创建好一个runloop时,就会将它放到事件队列中。每一个线程都有一个runloop,线程就会一直循环处理事件,直至没有需要处理的事件时才挂起。每个事件都关联着事件处理函数,事件在运行时通过message进行派发,message的类型由消息派发器预先设定。Mach在应用程序中创建了许多runloop,当创建好一个runloop时,就会将它放到事件队列中。
4. 应用程序是如何与dyld链接的?
- 运行时根据app代码中定义的动态符号(Symbol)并通过dyld进行符号查找来生成一个objsection。
- dyld根据objsection来生成一个objc_cache文件,文件通过写出需要被链接的符号值来实现。
- 运行时之后根据文件中的数据将符号添加到程序的动态数据结构中去。
- dyld执行最后一次符号查找。在dyld的hook中有个是objsection文件和运行时的结合。运行时根据文件以及Objective-C的运行时机制,开始进行类型和检查的动态分配。通过这些,app在dyld的hook中的hook里创建一个runloop,这个runloop为UI服务,直至main函数开始执行。
总结
通过这篇文章,我们可以了解到在main函数运行前,app做了如下:
- 首先创建一个叫launch的process,主要用于为app的运行做环境准备。
- 准备环境就绪后,就开始执行一个专用于UIThread(UI主线程)的runloop,用于处理用户点击等用户交互。
- 完成运行UI专门runloop,才进入到main函数中。
也只有了解完这些,才能对我们对dyld的hack做更深度的了解,在了解之前所产生的疑惑,比如为什么需要对dyld进行hack,以及dyld的hack能给我们解决什么问题。