iOS开发中,main函数是我们熟知的程序启动入口,但实际上并非真正意义上的入口,因为在我们运行程序,再到main方法被调用之间,程序已经做了许许多多的事情,比如我们熟知的runtime的初始化就发生在main函数调用前,还有程序动态库的加载链接也发生在这阶段。
整个过程为:
dyld返回main函数地址,main函数被调用
didFinishLaunchingWithOptions
总体可以分为main函数调用之前和main函数调用之后。
Mach-O文件格式是 OS X 与 iOS 系统上的可执行文件格式,像我们编译过程产生的.O文件,以及程序的可执行文件,动态库等都是Mach-O文件。有以下几种Mach-O
Mach-O的结构如下:
更多Mach-O可查看《Mac OS X ABI Mach-O File Format Reference》
有两种方式可以查看一个APP动态调用的系统可执行文件
1、通过machoview,选择APP的可执行文件,可以看到
2、通过otool -L
命令行查看
全程the dynamic loade,Apple 的动态链接器,系统 kernel 做好启动程序的初始准备后,交给 dyld 负责。
2017年,苹果引入了Dyld 3.0,但是只有系统APP采用这个,第三方APP都是采用Dyld 2.0。
Dyld 2.0的加载过程是:
main函数之后
这部分是主要的优化部分
能延迟初始化的尽量延迟初始化,不能延迟初始化的尽量放到后台初始化。
建一个类来管理初始化,所有需要初始化的代码都在这里进行,分类初始化:
1)、日志 / 统计等需要第一时间启动的, 仍然伴随 didFinishLaunchingWithOptions 启动.
2)、用户数据需要在广告显示完成以后使用, 所以需要伴随广告页启动。
3)、比如分享业务, 肯定是用户能看到真正的主界面以后才需要启动, 所以推迟到主界面加载完成以后启动, 只需要将代码放到方法里。
延迟初始化那些不必要的UIViewController
。
Time Profiler在分析时间占用上非常强大。实用的时候注意三点
Main函数之前
Main函数之前是iOS系统的工作,所以这部分的优化往往更具有通用性。
启动的第一步是加载动态库,加载系统的动态库使很快的,因为可以缓存,而加载内嵌的动态库速度较慢。所以,提高这一步的效率的关键是:减少动态库的数量。
Rebase和Bind都是为了解决指针引用的问题。对于Objective C开发来说,主要的时间消耗在Class/Method的符号加载上,所以常见的优化方案是:
__DATA
段中的指针数量。通常,我们会在+load
方法中进行method-swizzling,这也是Nshipster推荐的方式。
__atribute__((constructor))
的使用,而是在第一次访问的时候才用dispatch_once等方式初始化。category实际上是个结构体,与Class结构体类似
struct _category_t {
const char *name; // 类的名字,用于寻找类
struct _class_t *cls; // 编译期为空,在runtime通过name找到类后获得
const struct _method_list_t *instance_methods; // 实例方法
const struct _method_list_t *class_methods; // 类方法
const struct _protocol_list_t *protocols; // 协议
const struct _prop_list_t *properties; // 关联属性
};
首先来看什么是库,库(Library)说白了就是一段编译好的二进制代码,加上头文件就可以供别人使用。
什么时候我们会用到库呢?一种情况是某些代码需要给别人使用,但是我们不希望别人看到源码,就需要以库的形式进行封装,只暴露出头文件。另外一种情况是,对于某些不会进行大的改动的代码,我们想减少编译的时间,就可以把它打包成库,因为库是已经编译好的二进制了,编译的时候只需要 Link 一下,不会浪费编译时间。
上面提到库在使用的时候需要 Link,Link 的方式有两种,静态和动态,于是便产生了静态库和动态库。
静态库即静态链接库(Windows 下的 .lib,Linux 和 Mac 下的 .a)。之所以叫做静态,是因为静态库在编译的时候会被直接拷贝一份,复制到目标程序里,这段代码在目标程序里就不会再改变了。
静态库的好处很明显,编译完成之后,库文件实际上就没有作用了。目标程序没有外部依赖,直接就可以运行。当然其缺点也很明显,就是会使用目标程序的体积增大。
动态库即动态链接库(Windows 下的 .dll,Linux 下的 .so,Mac 下的 .dylib/.tbd)。与静态库相反,动态库在编译时并不会被拷贝到目标程序中,目标程序中只会存储指向动态库的引用。等到程序运行时,动态库才会被真正加载进来。
动态库的优点是,不需要拷贝到目标程序中,不会影响目标程序的体积,而且同一份库可以被多个程序使用(因为这个原因,动态库也被称作共享库)。同时,编译时才载入的特性,也可以让我们随时对库进行替换,而不需要重新编译代码。动态库带来的问题主要是,动态载入会带来一部分性能损失,使用动态库也会使得程序依赖于外部环境。如果环境缺少动态库或者库的版本不正确,就会导致程序无法运行(Linux 下喜闻乐见的 lib not found 错误)。
除了上面提到的 .a 和 .dylib/.tbd 之外,Mac OS/iOS 平台还可以使用 Framework。Framework 实际上是一种打包方式,将库的二进制文件,头文件和有关的资源文件打包到一起,方便管理和分发。
在 iOS 8 之前,iOS 平台不支持使用动态 Framework,开发者可以使用的 Framework 只有苹果自家的 UIKit.Framework,Foundation.Framework 等。这种限制可能是出于安全的考虑(见这里的讨论)。换一个角度讲,因为 iOS 应用都是运行在沙盒当中,不同的程序之间不能共享代码,同时动态下载代码又是被苹果明令禁止的,没办法发挥出动态库的优势,实际上动态库也就没有存在的必要了。
由于上面提到的限制,开发者想要在 iOS 平台共享代码,唯一的选择就是打包成静态库 .a 文件,同时附上头文件(例如微信的SDK)。但是这样的打包方式不够方便,使用时也比较麻烦,大家还是希望共享代码都能能像 Framework 一样,直接扔到工程里就可以用。于是人们想出了各种奇技淫巧去让 Xcode Build 出 iOS 可以使用的 Framework,具体做法参考这里和这里,这种方法产生的 Framework 还有 “伪”(Fake) Framework 和 “真”(Real) Framework 的区别。
iOS 8/Xcode 6 推出之后,iOS 平台添加了动态库的支持,同时 Xcode 6 也原生自带了 Framework 支持(动态和静态都可以),上面提到的的奇技淫巧也就没有必要了(新的做法参考这里)。为什么 iOS 8 要添加动态库的支持?唯一的理由大概就是 Extension 的出现。Extension 和 App 是两个分开的可执行文件,同时需要共享代码,这种情况下动态库的支持就是必不可少的了。但是这种动态 Framework 和系统的 UIKit.Framework 还是有很大区别。系统的 Framework 不需要拷贝到目标程序中,我们自己做出来的 Framework 哪怕是动态的,最后也还是要拷贝到 App 中(App 和 Extension 的 Bundle 是共享的),因此苹果又把这种 Framework 称为 Embedded Framework。
跟着 iOS8 / Xcode 6 同时发布的还有 Swift。如果要在项目中使用外部的代码,可选的方式只有两种,一种是把代码拷贝到工程中,另一种是用动态 Framework。使用静态库是不支持的。
造成这个问题的原因主要是 Swift 的运行库没有被包含在 iOS 系统中,而是会打包进 App 中(这也是造成 Swift App 体积大的原因),静态库会导致最终的目标程序中包含重复的运行库(这是苹果自家的解释)。同时拷贝 Runtime 这种做法也会导致在纯 ObjC 的项目中使用 Swift 库出现问题。苹果声称等到 Swift 的 Runtime 稳定之后会被加入到系统当中,到时候这个限制就会被去除了(参考这个问题 的问题描述,也是来自苹果自家文档)。
在纯 ObjC 的项目中,CocoaPods 使用编译静态库 .a 方法将代码集成到项目中。在 Pods 项目中的每个 target 都对应这一个 Pod 的静态库。不过在编译过程中并不会真的产出 .a 文件。如果需要 .a 文件的话,可以参考这里,或者使用 CocoasPods-Packager 这个插件。
当不想发布代码的时候,也可以使用 Framework 发布 Pod,CocoaPods 提供了 vendored_framework
选项来使用第三方 Framework,具体的做法可以参考这里和这里。
对于 Swift 项目,CocoaPods 提供了动态 Framework 的支持。通过 use_frameworks!
选项控制。对于 Swift 写的库来说,想通过 CocoaPods 引入工程,必须加入 use_frameworks!
选项。具体原因参见上一节对于 Swift 部分的介绍。
引用:
https://blog.csdn.net/hello_hwc/article/details/78317863
WWDC 2017: App Startup Time: Past, Present, and Future