iOS App启动优化:动态库手动加载

一、前言

在介绍动态库手动加载方式之前,我们简单了解下动态库,又名共享库在iOS中是个特殊的存在,除了系统库以外,在大部分使用场景下(除了App Extension可以共享)其实并不能达到共享的目的。在iOS开发中动态库主要有以下用途:

  • 解决苹果审核iOS8__Text 字段60M限制,将独立的代码封装到动态库,进而减小可执行文件代码段的大小。

  • 制作第三方库,因为动态库没有像静态库之间的符号冲突问题(Xcode会有冲突日志,不影响运行),很多时候第三方库往往会以动态库的形式存在。

不同于静态库会被一起链接到Mach-O文件中,动态库是独立于主程序存在的。我们使用动态库时一般是直接拖到工程中,设置下Embed,使用起来非常方便。这些动态库是在App启动的时候通过dyld(动态链接器)根据依赖关系递归的加载到内存中,这样的方式称为动态库自动加载。但是如果动态库数量多了,会大大的拖慢应用的启动速度,因为dyld在rebasebinding阶段比较耗时。

那么,对于动态库使用比较多的项目怎么去优化App启动的耗时呢?其实除了自动加载方式,还有一种是手动加载(也称为懒加载),我们可以将一些不常用的动态库模块使用手动加载方式。

二、使用

动态库手动加载有两种方式可以实现:

  • dlopen;

  • NSBundle load/loadAndReturnError;

苹果在审核条款中明确禁止使用dlopen(感谢@
iOSLover的分享,和审核团队确认:加签过的动态库可以使用dlopen。本人未做验证,仅供参考。),我们重点看下NSBundle load/loadAndReturnError的方式,load的方式底层也是使用dlopen实现,只是增加了验签,而签名是在App打包的时候完成。如果从其他途径(如网络下载)获取的动态库是无法完成验签的

手动方式加载方式如下:

  1. 在Build Phases中点击"+"-"New Copy Files Phase",新增Copy Files选项,如果有动态库Strip的脚本,需要将Copy Files拖到前面,保证在打包时可以执行去除i386/x86_64指令集;

  2. 修改Copy Files 中的Destination选项为Frameworks,这样手动加载的动态库也会和其他动态库拷贝到同一个目录,点击"+"-"Add Others..."添加需要手动加载的动态库;

    Xcode截图

现在,我们可以使用了(因为是动态加载的,调用方式也只能是动态调用):

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组件库等等...。而这些库可能是静态库,也可能是动态库。先看下静态库/动态库的打包时的依赖的特性:

  • 静态库依赖静态库,只引用,相互独立;

  • 静态库依赖动态库,只引用,相互独立;

  • 动态库依赖动态库,只引用,相互独立;

  • 动态库依赖静态库,链接到一起;

从上面的特性可以看出,动态库如果依赖静态库会“合并”静态库。这样被依赖的静态库在项目中有多份“拷贝”,这会大大增加包大小。制作动态库时可以这样做:

  1. 在动态库工程制作动态库的时候,删除Link Binary With Libraries中依赖的静态库,保留工程目录下的引用不要删除;
    Link Binary With Libraries
  1. "other linker Flags" 中添加-undefined dynamic_lookup
    other linker Flags

这样打包出来的动态库就不会包含静态库了...

因为动态库引用了主执行文件(静态库最后会被链接到主执行文件)的符号,所以主工程的配置也需要跟着修改:
"Build Settings"-"Strip Style" 修改为Non-Global Symbols,将外部引用的符号保留,当然这会略微增加包大小。

Strip Style

三、加载成功率 & 性能

从线上监控数据来看未发现加载失败的情况,成功率可达100%。加载耗时跟设备性能、特别是动态库符号(类名,协议,方法名等)数量有关。97% 以上几乎在用户无感知的情况下加载完成(毫秒级)。手动加载的效率比自动加载效率低,请勿在app启动过程中使用。

四、结束语

以上是动态库手动加载的使用方式,随着越来越多的App放弃iOS8,使用动态库来解决__Text 大小限制的需求变得越来越少。但可以作为App启动优化中动态库部分的优化方案(成本低,效果好)。对于组件化(如CocoaPods)构建的工程,上述的配置方案会有不同,但是原理一样。

你可能感兴趣的:(iOS App启动优化:动态库手动加载)