细说iOS静态库和动态库

12,106 阅读7分钟

细说iOS静态库和动态库

对于讲解iOS中静态库和动态库的文章已经很多了,之前自己也总结了一篇。从今天目前来说,之前就是总结了皮毛。今天的文章也算是后续的总结,应该随着后面的深入还会有不同的体会。

iOS中的静态库和动态库

静态库

  • 静态库完全复制进可执行的二进制里面
  • 后缀是.a或者.framework

动态库

  • 动态库是在程序冷启动时候被链接到手机内存或者 App 内存里面
  • 后缀是.tbd或者.framework

关于说明很多,可以看一下网上的文章。我现在说的是基于上面理论的证明。刚开始觉得网上说的可能不太准确,后来得到验证是正确的。

为了研究我们创建的.a.framework到底是静态库和动态库,我们分别创建对应的简单的库。

我们分别可以通过上面图中红色区域创建.framework.a。我们知道区分是静态库还是动态库最终是我们选择的 Mach-O 的类型到底是Dynamic Library还是Static Library

Mach-O 类型

这里简单说一下Mach-O类型,为什么说简单说一下。因为深入我也不了解了,深入可以谷歌资料。

  • Executable:应用的主要二进制

  • Dylib Library:动态链接库(又称DSO或DLL)

  • Static Library:静态链接库

  • Bundle:不能被链接的Dylib,只能在运行时使用dlopen( )加载,可当做macOS的插件

  • Relocatable Object File:可重定向文件类型

上面的解释也是抄别人的(小声).

静态库和动态库的对比

为了让打出来的库更加的真实,我们使用网上出名的库作为测试。我们选取的是FLEX作为测试的目标,因为这个库所包含的文件多,数据真实性更加的可靠。我们编译的环境是基于iPhone 6s Plus进行编译出来的,真正的大小会包含其他框架会比我测试大得多。

二进制大小

二进制大小(iPhone 6s Plus) .a .framework
静态库 6.8MB 4.6MB
动态库 1.6MB 1.6MB

为了验证我们所谓静态库和动态库是否是真正的静态库和动态库,我们使用File命令和Mach-O查看软件分别对比一下。

File 命令

输出显示 .a .framework
静态库 ✅(current ar archive) ✅(current ar archive)
动态库 ✅(Mach-O 64-bit dynamically linked shared library x86_64) ✅(Mach-O 64-bit dynamically linked shared library x86_64)

Mach-O 查看

输出显示 .a .framework
静态库 ✅(Static Library) ✅(Static Library)
动态库 ✅(Shared Library) ✅(Shared Library)

我们对比结果发现,不管是.a还是.framework都可以作为动态链接库来使用,这和我们在网上看到文章说.a是静态库是不严谨的。

我们按照静态库是完整被拷贝到工程二进制里面,动态库是在启动时候动态链接的描述,我们分别对比一下运行包里面的表现。我们同样是按照iPhone 11 Pro Max做为对比的,这样数据比较真实。

静态库和动态库在包里面的表现

Static Library.a.framework

代码所在的位置
代码所在位置(iPhone 6s Plus) .a .framework
静态库 App 二进制中 App 二进制和 Frameworks 文件夹中
动态库 无(二进制没有 Frameworks文件夹 没有) Frameworks 文件夹中
App 二进制大小对比
App二进制大小(iPhone 6s Plus) .a .framework
静态库 93KB 93KB
动态库 92KB 92KB
启动速度对比
冷启动
冷启动速度(iPhone 6s Plus) .a .framework
静态库 151.44毫秒 53.35毫秒
动态库 0 毫秒(运行报错) 254.58毫秒
热启动
热启动速度 .a .framework
静态库 160.99 毫秒 154.65 毫秒
动态库 0 毫秒(运行报错) 199.04 毫秒

这里描述的冷启动是指代程序第一次安装之后运行可能会加载系统动态库而造成启动时间边长,热启动是第二次启动不需要加载系统动态库。

通过上面的代码位置,二进制大小,冷启动速度和热启动速度大概可以得出一个结论。虽然严格来说.a是支持打包出来动态库的,但是代码不会复制进二进制,也不会存放在Frameworks里面。造成我们打出来的动态库.a在程序里面无法运行,也就换句话说.a只支持静态库特征了。

虽然.a的静态库的热启动比冷启动还要慢,但是这几毫秒的误差可以抛弃。可能基于我们手机已经打开了其他应用造成冷启动没有再次加载系统动态库而和热启动速度差不多。

但是对比.framework可以得知静态库的启动速度确实比动态库的加载速度快很多。而静态库的.framework会存在两份,所以安装包会变大,因为静态库的.framework直接复制进二进制里面,所以二进制会变大。

通过上面我们得出下面的结论

  • .a只支持静态库(打包出动态库也是支持的 只不过目前 iOS 不支持加载)
  • .framework静态库会存在于二进制和Frameworks两份。
  • 热启动会比冷启动快的多
  • 静态库比动态库加载快得多
  • 静态库.framework.a的二进制文件小的多
  • 静态库和动态库都推荐用.framework不推荐用.a

XCFrameworks

XCFrameworks 和之前 Framework 对比

说完了我们常用的动态库和静态库,我们说一下今年出的新特征XCFramework。根据之前一篇文章了解,XCFramework是为了取代之前的.framework的。

我很想深入的探究一下XCframeworks来谈一下用这个的好处,但是苹果对于这个的资料少之又少。我们通过真实的对比一下XCFrameworks的好处。

我们通过对比包含模拟器真机.framework和对比.xcframework对比一下数据。

对于生成多架构的.framework我们需要用到lipo命令进行合成,但是对于生成.xcframework可以用到我的xcbuild的脚本生成。

(X86_64 + ARM64) .framework .xcframework
文件大小 10.6MB 10.8MB
二进制大小 10.4MB 8.3MB+2.2MB
App 大小(iPhone 6S Plus Debug) 4.8MB 2.8MB
冷启动 169.8 毫秒 155.52毫秒
热启动 159.56 毫秒 153.62 毫秒

通过对比.xcframework.framework不但 App 大小少了很多,而且还对于启动速度提升了不少。

我们之前说的可以将.a和头文件打包到.xcframework中,我们下面尝试一下。

融合.a 和头文件

我们事先准备好模拟器和真机架构的.a和头文件,使用下面的命令创建一个XCFrameworks.

xcodebuild -create-xcframework -library <path> [-headers <path>] [-library <path> [-headers <path>]...] -output <path>

//Example 
xcodebuild -create-xcframework -library /Users/zhangxing/Library/Developer/Xcode/DerivedData/MyLibrary-frqlmvciureprnbnytogqzjagose/Build/Products/Debug-iphonesimulator/libMyLibrary.a -headers /Users/zhangxing/Library/Developer/Xcode/DerivedData/MyLibrary-frqlmvciureprnbnytogqzjagose/Build/Products/Debug-iphonesimulator/include/MyLibrary -library /Users/zhangxing/Library/Developer/Xcode/DerivedData/MyLibrary-frqlmvciureprnbnytogqzjagose/Build/Products/Debug-iphoneos/libMyLibrary.a -headers /Users/zhangxing/Library/Developer/Xcode/DerivedData/MyLibrary-frqlmvciureprnbnytogqzjagose/Build/Products/Debug-iphoneos/include/MyLibrary -output /Users/zhangxing/Library/Developer/Xcode/DerivedData/MyLibrary-frqlmvciureprnbnytogqzjagose/Build/Products/MyLibrary.xcframework

包含第多个不同的Framework(不支持)

假设我们的MyFramework依赖一个我们自己的另外的库或者其他第三方的库

xcodebuild -create-xcframework -framework <path> [-framework <path>...] -output <path>

//Example
xcodebuild -create-xcframework -framework /Users/zhangxing/Downloads/data/MyFrameworkB.xcframework/ios-arm64/MyFrameworkB.framework -framework /Users/zhangxing/Downloads/data/MyFrameworkB.xcframework/ios-x86_64-simulator/MyFrameworkB.framework -framework /Users/zhangxing/Downloads/data/ios-arm64/MyFramework.framework -framework /Users/zhangxing/Downloads/data/ios-x86_64-simulator/MyFramework.framework -output /Users/zhangxing/Downloads/data/MyFrameworkC.xcframework

结果是一个 XCFrameworks只能包含一个框架,但是可以包含多个架构。和之前写的文章写的理解有误,这里纠正一下。

怎么将之前已经Fat Framework变成最新的XCFramework

如果有源码可以直接使用xcbuild命令生成,如果没有或者是其他第三方的可以使用下面的命令。

 lipo 静态库源文件路径 -thin CPU架构名称 -output 拆分后文件存放路径

用分离出来的架构重新合成XCFrmaework

其实这样很麻烦,之后我会有空完善一下xcbuild的功能。写到这里这篇文章就写完了,对于静态库和动态库知道了一些,还有一些疑惑。对于XCFrmaework验证了一些猜想,也存在太多的疑问。

掘金年度征文 | 2019 与我的技术之路 征文活动正在进行中......