# 09.深入理解动态库与静态库

## 一、简单的区别

`静态`和`动态`是相对`编译时`和`运行时`进行区分的

### 1.1 静态库

静态库在编译时会被链接进MachO中

后缀一般为`.a`/`.framework`

### 1.2 动态库

在程序编译时不会被链接到MachO中，而是在程序启动的时候加载动态库到内存中。其中动态库分`动态链接库`和`动态加载库`两种

动态库文件一般为`.tbd(以前是.dylib)`/`.framework`

#### 动态链接库

在编译阶段确定了依赖关系的动态库。当运行可执行文件时，如果尚未加载，则会将加载这些动态库。

> 常见的动态库都是这种

#### 动态加载库

编译阶段不需要指定app需要依赖哪些动态库。当运行过程中需要加载某个动态库时，就会用`dlopen`函数动态的把库加载到内存中使用。

> 这种玩玩可以，上架就别想了。比通过网络如下载一个动态库，然后加载调用。

### 1.3 体积

一般来说，静态库直接拷贝进可执行文件，相较动态库对包体积的影响会大一些。

静态库被多个可执行文件依赖就会被拷贝多次。

### 1.4 启动速度

因为动态库在应用启动的时候需要加载，所以对应用的启动速度是有一定的影响的。

严谨的说，对于已经加载过的动态库已经存在于共享缓存中，那么对启动速度的影响就很小了。

### 1.5 各自优缺点

* 静态库
  * 模块化，分工合作，提高了代码的复用及核心技术的保密程度
  * 避免少量改动经常导致大量的重复编译连接
  * 也可以重用，注意不是共享使用
* 动态库
  * 可以将最终可执行文件体积缩小，将整个应用程序分模块，团队合作，进行分工，影响比较小
  * 多个应用程序共享内存中得同一份库文件，节省资源
  * 可以不重新编译连接可执行程序的前提下，更新动态库文件达到更新应用程序的目的
  * 应用插件化
  * 软件版本实时模块升级
  * 在其它大部分平台上，动态库都可以用于不同应用间共享， 共享可执行文件，这就大大节省了内存

> 在 `iOS8` 之前，苹果不允许第三方框架使用动态方式加载，从 iOS8 开始允许开发者有条件地创建和使用动态框架，这种框架叫做 `Cocoa Touch Framework`。
>
> 虽然同样是动态框架，但是和系统 `framework` 不同，苹果系统专属的 `framework` 是共享的（如 `UIKit`），使用 `Cocoa Touch Framework` 制作的动态库在打包和提交 app 时会被放到 `app main bundle` 的根目录中，运行在`沙盒`里，而不是系统中。
>
> 也就是说，不同的 app 就算使用了同样的 framework，但还是会有多份的框架被分别签名、打包和加载。
>
> 不过 iOS8 上开放了 `App Extension` 功能，可以为一个应用创建插件，这样主 app 和插件之间共享动态库还是可行的。

## 二、什么是framework

Framework 是 Cocoa/Cocoa Touch 程序中使用的一种资源打包方式，可以将代码文件、头文件、资源文件、说明文档等集中在一起，方便开发者使用。一般如果是静态 Framework 的话，资源打包进 Framework 是读取不了的。静态 Framework 和 .a 文件都是编译进可执行文件里面的。只有动态 Framework 能在 .app 下面的 Framework 文件夹下看到，并读取 .framework 里的资源文件。

Cocoa/Cocoa Touch 开发框架本身提供了大量的 Framework，比如 Foundation.framework/UIKit.framework 等。需要注意的是，这些 Framework 无一例外都是动态库。

平时用的第三方 SDK 的 Framework 都是静态库，真正的动态库是上不了 AppStore（iOS8 之后能上 AppStore，因为 App Extension，需要动态库支持)。

### 2.1 结构

#### Headers

表示暴露的头文件，一般都会有一个和 Framework 同名的 .h 文件，在创建 Framework 的时候文件夹里也会默认生成这样一个文件。有这个和 Framework 同名的 .h 文件 @import 导入库的时候编译器才能找到这个库。

#### info.plist

主要就是这个 Framework 的一些配置信息。

#### Modules

这个文件夹里有个 module.modulemap 文件

```
framework module DynamicFramework {
  umbrella header "DynamicFramework.h"

  export *

  module * { export * }

}
```

这里面有这样一句 umbrella header "DynamicFramework.h"，umbrella 有保护伞、庇护的意思。

也就是说 Headers 中暴露的 DynamicFramework.h 文件被放在 umbrella 雨伞下保护起来了，所以我们需要将其他的所有需要暴露的 .h 文件放到 DynamicFramework.h 文件中保护起来，不然会出现警告。

@import 的时候也只能找到 umbrella 雨伞下保护起来的 .h 文件。

#### 二进制文件（Unix 可执行文件）

这个就是你源码编译而成的二进制文件，主要的执行代码就在这个里面。

#### .bundle 文件

如果我们在 Build Phases -> Copy Bundle Resources 里加入 .bundle 文件，那么创建出来的 .Framework 里就会有这个 .bundle 的资源文件夹。

### 2.2 资源文件

`CocoaPods` 如何生成 `Framework` 的资源文件？

我们能看到用 `cocoapods` 创建 `Framework` `的时候，Framework` 里面有一个 `.bundle` 文件，跟 `Framework` 同级目录里也有一个 `.bundle`文件。这两个文件其实是一样的。

那这两个 `.bundle` 是怎么来的呢？我们能看到用 `use_frameworks!` 生成的 pod 里面，pods 这个 PROJECT 下面会为每一个 `pod` 生成一个 `target。`

![1](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F9c17c5813dccf93b2c1d7344e6bac44e183274bd.png?generation=1620563801977803\&alt=media)

那么如果这个 `pod` 有资源文件的话，就会有一个叫 `xxx-bundleName` 的 `target`，最后这个 `target` 生成的就是 `bundleName.bundle`。

![2](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F6162e07a0c974406e837db4544974e62bbde2471.png?generation=1620563800390315\&alt=media)

在 xxx 的 target 的`Build Phases -> Copy Bundle Resources`里加入这个 .bundle，在 Framework 里面就会生成这样一个 bundle。

在 xxx 的 target 的`Build Phases -> Target Dependencies`里加入这个`target：xxx-bundleName`，就会在 Framework 的同级目录里生成这样一个 bundle。

静态 Framework 里不需要加入资源文件。一般资源打包进静态 Framework 是读取不了的。

静态 Framework 和 .a 文件都是编译进可执行文件里面的。只有动态 Framework 能在 .app 的 Framework 文件夹下看到，并读取 .framework 里的资源文件。

你可以用 `NSBundle * bundle = [[NSBundle mainBundle] bundlePath];` 得到 `.app` 目录，如果是动态库你能在 `Framework` 目录下看到这个动态库以及动态库里面资源文件。

然后你只要用 `NSBundle * bundle = [NSBundle bundleForClass:<#ClassFromFramework#>];` 得到这个动态库的路径就能读取到里面的资源了。但是如果是静态库的话，因为编译进了可执行文件里面，你也就没办法读到这个静态库了，你能看到 .app 下的 Framework 目录为空。

### 2.3 Embedded Framework

`报错 Reason: image not found`

如果直接在工程里使用创建的动态库时候会出现此错误，需要在工程的 General 里的 Embedded Binaries 添加这个动态库才能使用。

因为创建的这个动态库其实也不能给其他程序使用的，而你的 App Extension 和 APP 之间是需要使用这个动态库的。这个动态库可以 App Extension 和 APP 之间共用一份（App 和 Extension 的 Bundle 是共享的），因此苹果又把这种 Framework 称为 Embedded Framework。

## 三、CocoaPods

在使用 `CocoaPods` 的时候在 Podfile 里加入 `use_frameworks!`，那么你在编译的时候就会默认帮你生成动态库，我们能看到每个源码 Pod 都会在 Pods 工程下面生成一个对应的动态库 `Framework` 的 `target，我们能在这个` `target` 的 `Build Settings -> Mach-O Type` 看到默认设置是 `Dynamic Library`，也就是会生成一个动态 `Framework`，我们能在 `Products` 下面看到每一个 Pod 对应生成的动态库。

这些生成的动态库将链接到主项目给主工程使用，但是我们上面说过动态库需要在主工程 `target` 的 `General -> Embedded Binaries` 中添加才能使用，而我们并没有在 `Embedded Binaries` 中看到这些动态库。那这是怎么回事呢，其实是 `cocoapods` 已经执行了脚本把这些动态库嵌入到了 `.app` 的 `Framework` 目录下，相当于在 `Embedded Binaries` 加入了这些动态库。我们能在主工程 `target` 的 `Build Phase -> Embed Pods Frameworks` 里看到执行的脚本。

## 参考

[iOS 静态库和动态库](https://www.cnblogs.com/dins/p/ios-jing-tai-ku-he-dong-tai-ku.html) [初识iOS中的动态库和静态库](https://freelf.me/%E5%88%9D%E8%AF%86iOS%E4%B8%AD%E7%9A%84%E5%8A%A8%E6%80%81%E5%BA%93%E5%92%8C%E9%9D%99%E6%80%81%E5%BA%93)
