做iOS开发日常不怎么涉及到这些内容,但总会遇见相关的bug或者一些优化需求。下面整理学习一些知识。
程序启动过程发生了什么?
启动后发生了什么?
程序启动后的生命周期是怎么样的?
如何优化启动时间?
App启动过程
iOS开发中,main函数是我们熟知的程序启动入口,但实际上并非真正意义上的入口,因为在我们运行程序,再到main方法被调用之间,程序已经做了许许多多的事情,比如我们熟知的runtime的初始化就发生在main函数调用前,还有程序动态库的加载链接也发生在这阶段。
1、系统先读取App的可执行文件(Mach-O文件),获取到dyld的路径,并加载dyld(动态库链接程序)。
2、dyld去初始化运行环境、开启缓存策略、加载依赖库、我们的可执行文件、链接依赖库,并调用每个依赖库的初始化方法。
3、在上一步runtime被初始化,当所有的依赖库初始化后,程序可执行文件进行初始化,这个时候runtime会对项目中的所有类进行类结构初始化,然后调用所有类的+load方法。
1、runtime初始化方法 _objc_init 中最后注册了两个通知:
map_images: 主要是在镜像加载进内容后对其二进制内容进行解析,初始化里面的类结构等
load_images: 主要是调用call_load_methods 按照继承层次依次调用Class的 +load方法 然后是Category的+ load方法。(call_load_methods 调用load 是通过方法地址直接调用的load方法,并不是通过消息机制,这就是为什么分类中的load方法并不会覆盖主类以及其他同主类的分类里的load 方法实现了。)
2、runtime 调用项目中所有的load方法时,所有的类的结构已经初始化了,此时在load方法中可以使用任何类创建实例并给他们发送消息。
4、最后dyld返回main函数地址,main函数被调用。
App启动后
App启动后执行流程
main() -> UIApplicationMain() -> UIApplication 构建、Appdelegate 构建、info.plist文件加载
UIApplication -> runloop创建
Appdelegate(代理) -> didFinishLaunchingWithOptions 函数执行( window构建、RootVC构建)
app运行的五种状态
ios app有5种状态,分别是:
1、Not running未运行:app没启动或被迫终止。
2、Inactive未激活:当前应用正在前台运行,但是并不接收事件(当前或许正在执行其它代码)。一般每当应用要从一个状态切换到另一个不同的状态时,中途过渡会短暂停留在此状态。唯一在此状态停留时间比较长的情况是:当用户锁屏时,或者系统提示用户去响应某些(诸如电话来电、有未读短信等)事件的时候。
3、Active激活:当前应用正在前台运行,并且接收事件。这是应用正在前台运行时所处的正常状态。
4、Backgroud后台:程序在后台而且能执行代码,大多数程序进入这个状态后会在在这个状态上停留一会。时间到之后会进入挂起状态(Suspended)。经过特殊的请求后可以长期处于Backgroud状态。
5、Suspended挂起:程序在后台不能执行代码。系统会自动把程序变成这个状态而且不会发出通知。当挂起时,程序还是停留在内存中的,当系统内存低时,系统就把挂起的程序清除掉,为前台程序提供更多的内存。
App的生命周期
application:didFinishLaunchingWithOptions: 这是程序启动时调用的函数。
applicationDidBecomeActive: 应用在准备进入前台运行时执行的函数。
applicationWillResignActive: 应用当前正要从前台运行状态离开时执行的函数。
applicationDidEnterBackground: 此时应用处在background状态,并且没有执行任何代码,未来将被挂起进入suspended状态。
applicationWillEnterForeground: 当前应用正从后台移入前台运行状态,但是当前还没有到Active状态时执行的函数。
applicationWillTerminate: 当前应用即将被终止,在终止前调用的函数。如果应用当前处在suspended,此方法不会被调用。
启动时间优化
启动时间
启动时间是用户点击App图标,到第一个界面展示的时间
启动时间在小于400ms是最佳的,因为从点击图标到显示Launch Screen,到Launch Screen消失这段时间是400ms。启动时间不可以大于20s,否则会被系统杀掉。
在Xcode中,可以通过设置环境变量来查看App的启动时间,DYLD_PRINT_STATISTICS和DYLD_PRINT_STATISTICS_DETAILS。
Edit Scheme - Arguments - Environment Variables
冷启动 和 热启动
如果你刚刚启动过App,这时候App的启动所需要的数据仍然在缓存中,再次启动的时候称为热启动。如果设备刚刚重启,然后启动App,这时候称为冷启动。
优化启动时间
用Time Profiler 检查一遍自己的App。
main函数之前的优化苹果本身已经处理了很好,一般而言,不再需要我们去调整,调整性价比也会比我们调整自己的代码低很多,所以优化启动时间大部分是优化我们自己写的代码。
优化main函数之前的操作
动态库加载优化
加载系统的动态库使很快的,因为可以缓存,而加载内嵌的动态库速度较慢。
所以,提高这一步的效率的关键是:减少我们自己的动态库的数量。
之前公司进行项目模块化的时候拆出来很多库,pod一加载 30几个pod,编译都慢成狗,这种情况建议合并动态库,比如公司内部由私有Pod建立了如下动态库:XXTableView, XXHUD, XXLabel,强烈建议合并成一个XXUIKit来提高加载速度。
runtime初始化优化
合并Category和功能类似的类
删除废弃代码
其他
+load 中少做swizzle 等操作
不要在 Initializers 创建线程等操作
多用swift 静态分发
优化我们自己的代码
从main函数开始执行,到你的第一个界面显示,这期间一般会做的事情:
1、执行AppDelegate的代理方法,主要是didFinishLaunchingWithOptions
2、初始化Window,初始化基础的ViewController结构(一般是UINavigationController+UITabViewController)
3、获取数据(Local DB/Network),展示给用户。
处理方向:
AppDelegate 中的方法
didFinishLaunchingWithOptions 方法
applicationDidBecomeActive 方法
能延迟初始化的尽量延迟初始化,不能延迟初始化的尽量放到后台初始化。
第一个页面展示
延迟初始化那些不必要的 UIViewController
以上两个地方不使用大量占用主线程资源的操作,数据获取解析等放在异步处理。