我们讲解多工程联编时,首先要明白为什么要多工程联编,怎么多工程联编,多工程联编的优劣点
一.首先我们介绍一下为什么要多工程联编
因为公司的项目越来越大了,业务端代码都是混乱管理,造成开发有很多痛点无法单测,无法统一管理同功能模块,快速定位bug,维护成本大,效率低,团队成员提交代码冲突机率大,CI配合效果差,功能性代码多端无法复用,编译时间长 等等痛点,所以要将项目进行模块化,组件化管理,同一个模块封装成一个工程,静态库,维护起来就高效多.下次在开发新项目时,就可以把这个工程引入进来,要用里面的功能时就可以直接使用,要我们维护好这个静态库工程,以后开发就会省事不少。
二.怎么创建多工程
对一个项目来说,将封装好的静态库直接拖到主工程里,引入头文件,详细可以网上搜索
三.多工程联编的优劣点
这个是重点,有关编译效率问题,性能问题,如果不好,比之前还慢,那就没有必要了,
1.APP打包运行原理:Objective和Swift都是编译语言,换句话说都是需要编译才能执行的
不管是OC还是Swift,都是采用Clang作为编译器前端,LLVM(Low level vritual machine)作为编译器后端。所以简单的编译过程如图

编译器前端:编译器前端的任务是进行:语法分析,语义分析,生成中间代码(intermediate representation )。在这个过程中,会进行类型检查,如果发现错误或者警告会标注出来在哪一行
编译器后端:编译器后端会进行机器无关的代码优化,生成机器语言,并且进行机器相关的代码优化
执行一次XCode build的流程:当你在XCode中,选择build的时候(快捷键command+B),会执行如下过程
1),编译信息写入辅助文件,创建编译后的文件架构(name.app)
2),处理文件打包信息
3),执行CocoaPod编译前脚本,例如对于使用CocoaPod的工程会执行CheckPods Manifest.lock
4),编译各个.m文件,使用CompileC和clang命令
5),链接需要的Framework,例如Foundation.framework,AFNetworking.framework,ALiPay.fframework
6),编译xib文件
7),拷贝xib,图片等资源文件到结果目录
8),编译ImageAssets
9),处理info.plist
10),拷贝Swift标准库
11),创建.app文件和对其签名
我们在每次编译过后,都会生成一个dsym文件。dsym文件中,存储了16进制的函数地址映射。在App实际执行的二进制文件中,是通过地址来调用方法的
在APP启动运行时:
系统首先加载可执行文件(自身App的所有.o文件的集合),然后加载动态链接库dyld,dyld是一个专门用来加载动态链接库的库。 执行从dyld开始,dyld从可执行文件的依赖开始, 递归加载所有的依赖动态链接库。
动态链接库包括:iOS 中用到的所有系统 framework,加载OC runtime方法的libobjc,系统级别的libSystem,例如libdispatch(GCD)和libsystem_blocks (Block)。
其实无论对于系统的动态链接库还是对于App本身的可执行文件而言,他们都算是image(镜像),而每个App都是以image(镜像)为单位进行加载的
image究竟包括哪些呢?
1).executable可执行文件 比如.o文件。
2).dylib 动态链接库 framework就是动态链接库和相应资源包含在一起的一个文件夹结构。
3).bundle 资源文件 只能用dlopen加载,不推荐使用这种方式加载。

如上图所示,不同进程之间共用系统dylib的_TEXT区,但是各自维护对应的_DATA区
所有动态链接库和我们App中的静态库.a和所有类文件编译后的.o文件最终都是由dyld(the dynamic link editor),Apple的动态链接器来加载到内存中。每个image都是由一个叫做ImageLoader的类来负责加载(一一对应)
什么是ImageLoader
image 表示一个二进制文件(可执行文件或 so 文件),里面是被编译过的符号、代码等,所以 ImageLoader 作用是将这些文件加载进内存,且每一个文件对应一个ImageLoader实例来负责加载。
两步走:
在程序运行时它先将动态链接的 image 递归加载 (也就是上面测试栈中一串的递归调用的时刻)。
再从可执行文件 image 递归加载所有符号。
动态链接库加载的具体流程
1).load dylibs image 读取库镜像文件
2).Rebase image
3).Bind image
4).Objc setup
5).initializers
后面会详细介绍,这里不做介绍
总结有一点:减少非系统库的依赖,减少不必要的framework,因为动态链接比较耗时
2,我们进行封装时,可以封装成静态库和动态库
先介绍一下静态库和动态库
动态库形式:.dylib和.framework
静态库形式:.a和.framework
静态库:
1),首先将源文件编译成目标文件:gcc –c a.c b.c,
2),生成静态库:ar –rc libstatic.a a.o b.o
当程序与静态库连接时,库中目标文件所含的所有将被程序使用的函数的机器码被copy到最终的可执行文件中。这就会导致最终生成的可执行代码量相对变多,相当于编译器将代码补充完整了,这样运行起来相对就快些。不过会有个缺点: 占用磁盘和内存空间. 静态库会被添加到和它连接的每个程序中, 而且这些程序运行时, 都会被加载到内存中. 无形中又多消耗了更多的内存空间.
官网文档说明:

动态库:
1),同静态库一样编译成目标文件:gcc –c a.c b.c
2),生成共享库:gcc –fPIC –shared –o libshared.so a.o b.o
由此可见静态库和动态库都是对目标文件的处理,也可以说库文件已经是机器码文件了
与共享库连接的可执行文件只包含它需要的函数的引用表,而不是所有的函数代码,只有在程序执行时, 那些需要的函数代码才被拷贝到内存中。这样就使可执行文件比较小, 节省磁盘空间,更进一步,操作系统使用虚拟内存,使得一份共享库驻留在内存中被多个程序使用,也同时节约了内存。不过由于运行时要去链接库会花费一定的时间,执行速度相对会慢一些,总的来说静态库是牺牲了空间效率,换取了时间效率,共享库是牺牲了时间效率换取了空间效率.
官网文档说明:

2,编译分析,性能问题
上面讲到了打包的原理,包括Apple提供的静态库和动态库解析原理,多工程联编无非我们关心是编译时间,效率问题,及封装成库的之间相互调用效率问题
1),在编译效率上,打包成Framework或者 static library,这样编译的时候这部分代码就不需要重新编译了,从这里可以看出封装成framework,编译时间跟之前是一样,有时会更少

2),封装成静态库调用效率,性能问题

而动态库,在调用时比较耗时,上面介绍动态库有官网文档截图
注意:Apple对待第三方开发者使用动态库的态度却是极端的否定,所以在iOS 7之前如果使用动态库是肯定会被reject的,reason。但在2014年Xcode6和iOS 8发布时却开放了这个禁地,应该主要是为了App Extension
Swift 与 Framework 的关系
在Xcode 6.0 Beta 4的 Release Notes 中,可以找到这句话:

共享可执行文件 iOS 有沙箱机制,不能跨App间共享共态库,但Apple开放了App Extension,可以在App和Extension间共间动态库(这也许是Apple开放动态链接库的唯一原因了)
这个就引入必须要用到动态库,嵌入式动态库Embedded Framework
官网文档说明:

一般我们项目都是cocoapod管理第三方,cocoapod必须use_frameworks,
cocoapods 会生成相应的 .frameworks文件(动态链接库:实际内容为 Header + 动态链接库 + 资源文件),使用 dynamicframeworks 来取代 staticlibraries 方式
3,总结,封装模块,静态库/动态库在编译效率会快点,但是在相互之间调用效率没有直接调用快,封装成静态库调用,效率相差不大,由于swift项目里,必须是动态链接库,所以调用效率方面会略差一点,而且封装成库也会引入依赖库,也会影响效率,这是一个取舍问题,封装成库,便于管理,定位bug,维护起来成本也低
网友评论