面试闪耀旅途

iOS 中的静态库与动态库

2021-01-24  本文已影响0人  闪耀旅途

如果你经常困惑 iOS 开发中的静态库和动态库的作用与区别, 那么这篇文章可以为你解惑

himg himg

静态库 (Static Libraries)

静态库简单的理解是多个目标文件 (object file, 以 .o 为后缀) 的打包集合. 静态库的存在形式:

查看 object file 格式: objdump -macho -section-headers /bin/ls

优势

动态库 (Dynamic Libraries)

动态库 (Dynamic Libraries, 也称作 Shared Library, Shared object, 动态链接库), 跟静态库一样是多个 object files 封装起来的, 但是动态库并不会在编译时直接置入 app, 而是将动态库的信息置入 app, 然后 app 在被运行的时候去动态查找动态库并进行链接, 这一步也叫做 动态链接.

根据动态库的载入时间 (load time) 我们将动态库分为以下两种:

以上行为是由动态链接器 (Dynamic linker, macOS 称 dyld) 来完成

动态库的存在形式分为以下几种:

macOS 大规模地使用 shared libraries, 可以前往路径 /usr/lib 文件夹查看系统的动态库.

himg

然而在运行时进行才做链接其实是一个笨重的负担, 应合理安排哪些库需要 load 以及时机.

优势

因为动态库不需要在编译时置入 app 中, 因此理论上体积会更小, 而且可以做到动态库内容改变所有结果文件不需要重新编译即可获得最新功能

以上只是对于标准的系统动态库来说的, 对于 iOS 开发来说, 因为我们只能使用 Embedding Frameworks 来使用动态库, 这样的动态库并不是真正的动态库, 其会在编译时全部置入 app, 然后在 app 启动时全部加载, 这样的话会导致体积大, 加载速度慢.

iOS 开发中 .framework 及动 / 静态库的区分

标准的动态库与静态库定义如上, 但是在 iOS 系统中, Apple 为我们提出了另一种可以包含依赖库的模式 -- .framework

一个 .framework 其实就是一个有着特定结构的文件夹装着各种共享的资源. 这些资源通常是 图片, Xibs, 动态库, 静态库, 文档 等, .framework 毫不掩饰的表明它纯粹就是一个文件夹.

himg

由于有 .framework 的存在, 我们在判断一个库到底是静态库还是动态库就有了麻烦, 因为一个 .framework 既可以是动态库也可以是静态库, 依赖于其内部的文件类型, 而.framework 中的二进制文件有可能有后缀, 也有可能没有后缀.

himg

为了区分其类型我们可以借助MachOView, 或者是在 Xcode 的 Targets -> build setting 中查找 mach-o type 选项.

himg

动静态库的混用

我们可以在一个项目中使用一部分动态库, 再使用一部分静态库, 如果涉及到第三方库与库之间的依赖关系时, 那么遵守如下原则:

结合实际 - CocoaPods 中的动态库静态库使用

静态库使用

默认情况下, 当我们在 Podfile 文件中写下:

platform :ios, '10.0'
source 'https://cdn.cocoapods.org/'

target 'HLTest' do
  pod 'AsyncSwift'
end

的时候, cocoapods 默认会使用静态库, 我们可以在 Products 文件夹中看到编译出的 .a 文件

himg

在项目的 .app 中, 我们可以看到静态库被编译进入可执行文件 (mach-o 文件), 导致文件大小为 14.9M

himg

动态库使用

cocoapods 提供了 use_frameworks! 选项让我们可以以 .framework 的形式导入第三方库, cocoapods 默认我们开启了此选项后在 .framework 文件夹中放的是动态库, 因此我们可以在 Podfile 中加入 use_frameworks! 来达到引入动态库的效果, 如下:

platform :ios, '10.0'
source 'https://cdn.cocoapods.org/'
use_frameworks!

target 'HLTest' do
  pod 'AsyncSwift'
end

然后经过 pod update 之后, 结果如下:

himg

cocoapods 编译生成的结果文件已经变为了 .framework 文件夹

再来看项目结果文件 .app:

himg

我们可以看到

cocoapods 中混合使用动静态库

动静态库的混用 中我们我们知道动态库不能依赖静态库, 因此在实际项目中会有一种需要特别注意的情况: 如果项目中有一个库必须是静态库时, 那么其整个依赖链路上的所有库都必须以静态库被引入, 如下图:

himg

库 4 为静态库的情况下, 整个依赖链路上的所有库(库 5库 3)都必须以静态库形式被项目依赖

这时我们需要使用 cocoapods 在版本 1.5 之后推出的新功能: s.static_frameworks = true. 这个命令使用在库的 .podspec 文件中, 用来指定本库作为静态库被其他项目作为 包含静态库的 .framework 文件 引入. 这样我们就可以在开发库的时候手动指定本库被以静态库还是动态库形式被引入了.

动态库的加载时机以及为什么动态库不能动态加载

在 iOS app 启动时系统会查找我们所依赖的所有动态库并加载, 这降低了我们 App 的启动速度, 那么可不可以将动态库的调用时间延迟到 app 运行时? 答案是不能!

不能动态加载动态库的原因是系统的限制. 查看苹果的 API 文档, 会发现有一个方法提供了加载可执行文件的功能, 那就是 NSBundleload 方法 (底层实现为 dlopen 函数), 如下所示:

img

然而, 这个方法的使用是有前提的. 那就是库和 app 的签名必需一致. iOS 可能是出于安全考虑, 在加载可执行代码前, 需要校验签名. load 方法的内部实现是调用了 dlopen, 而真机的 dlopen 内部还会调用 dlopen_preflight 先校验签名. 如果库不是事先打包进 app(打包进 app 的话会与 app 有相同签名), 就会报签名错误, 从而加载不成功. 如下图所示:

himg himg

因此动态加载加载动态库在模拟器上可以实现, 但是真机上不能运行

那么肯定有人会问, 既然无法加载成功, 苹果为什么要提供这个方法? 答案是, 虽然 iOS 无法使用, 但是 Mac OS 可以使用, 很明显这个方法目前是提供给 Mac OS 使用的. 如果以后系统放开签名校验, 那么 iOS 中也就可以动态加载了.

总结


最后

本文作者 Hanley Lee, 首发于 闪耀旅途, 如果对本文比较认可, 欢迎 Follow

参考

上一篇 下一篇

猜你喜欢

热点阅读