一、前言
在介绍动态库手动加载方式之前,我们简单了解下动态库,又名共享库在iOS中是个特殊的存在,除了系统库以外,在大部分使用场景下(除了App Extension可以共享)其实并不能达到共享的目的。在iOS开发中动态库主要有以下用途:
解决苹果审核iOS8
__Text
字段60M限制,将独立的代码封装到动态库,进而减小可执行文件代码段的大小。制作第三方库,因为动态库没有像静态库之间的符号冲突问题(Xcode会有冲突日志,不影响运行),很多时候第三方库往往会以动态库的形式存在。
不同于静态库会被一起链接到Mach-O文件中,动态库是独立于主程序存在的。我们使用动态库时一般是直接拖到工程中,设置下Embed,使用起来非常方便。这些动态库是在App启动的时候通过dyld(动态链接器)根据依赖关系递归的加载到内存中,这样的方式称为动态库自动加载。但是如果动态库数量多了,会大大的拖慢应用的启动速度,因为dyld在rebase
和binding
阶段比较耗时。
那么,对于动态库使用比较多的项目怎么去优化App启动的耗时呢?其实除了自动加载方式,还有一种是手动加载(也称为懒加载),我们可以将一些不常用的动态库模块使用手动加载方式。
二、使用
动态库手动加载有两种方式可以实现:
dlopen;
NSBundle load/loadAndReturnError;
苹果在审核条款中明确禁止使用dlopen(感谢@
iOSLover的分享,和审核团队确认:加签过的动态库可以使用dlopen。本人未做验证,仅供参考。),我们重点看下NSBundle load/loadAndReturnError的方式,load的方式底层也是使用dlopen实现,只是增加了验签,而签名是在App打包的时候完成。如果从其他途径(如网络下载)获取的动态库是无法完成验签的。
手动方式加载方式如下:
在Build Phases中点击"+"-"New Copy Files Phase",新增
Copy Files
选项,如果有动态库Strip的脚本,需要将Copy Files
拖到前面,保证在打包时可以执行去除i386/x86_64指令集;-
修改Copy Files 中的
Destination
选项为Frameworks
,这样手动加载的动态库也会和其他动态库拷贝到同一个目录,点击"+"-"Add Others..."添加需要手动加载的动态库;
现在,我们可以使用了(因为是动态加载的,调用方式也只能是动态调用):
NSString *path = [[NSBundle mainBundle] pathForResource:@"MyLib" ofType:@"framework" inDirectory:@"Frameworks"];
NSError *err = nil;
NSBundle *bundle = [NSBundle bundleWithPath:path];
if ([bundle loadAndReturnError:&err]) {
//加载成功,方法调用
Class c = NSClassFromString(@"MyClass");
[c performSelector:@selector(printLog)];
}
else {
//加载失败
}
二、扩展
上面的使用方式比较适合没有依赖的动态库。那么,我们能不能将一个业务模块转成动态库呢?业务模块往往会依赖各种各样的库,如网络库,埋点库,UI组件库等等...。而这些库可能是静态库,也可能是动态库。先看下静态库/动态库的打包时的依赖的特性:
静态库依赖静态库,只引用,相互独立;
静态库依赖动态库,只引用,相互独立;
动态库依赖动态库,只引用,相互独立;
动态库依赖静态库,链接到一起;
从上面的特性可以看出,动态库如果依赖静态库会“合并”静态库。这样被依赖的静态库在项目中有多份“拷贝”,这会大大增加包大小。制作动态库时可以这样做:
- 在动态库工程制作动态库的时候,删除
Link Binary With Libraries
中依赖的静态库,保留工程目录下的引用不要删除;
- "other linker Flags" 中添加
-undefined dynamic_lookup
;
这样打包出来的动态库就不会包含静态库了...
因为动态库引用了主执行文件(静态库最后会被链接到主执行文件)的符号,所以主工程的配置也需要跟着修改:
"Build Settings"-"Strip Style" 修改为Non-Global Symbols
,将外部引用的符号保留,当然这会略微增加包大小。
三、加载成功率 & 性能
从线上监控数据来看未发现加载失败的情况,成功率可达100%。加载耗时跟设备性能、特别是动态库符号(类名,协议,方法名等)数量有关。97% 以上几乎在用户无感知的情况下加载完成(毫秒级)。手动加载的效率比自动加载效率低,请勿在app启动过程中使用。
四、结束语
以上是动态库手动加载的使用方式,随着越来越多的App放弃iOS8,使用动态库来解决__Text
大小限制的需求变得越来越少。但可以作为App启动优化中动态库部分的优化方案(成本低,效果好)。对于组件化(如CocoaPods)构建的工程,上述的配置方案会有不同,但是原理一样。