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。

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

在 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 工程下面生成一个对应的动态库 Frameworktarget,我们能在这个 targetBuild Settings -> Mach-O Type 看到默认设置是 Dynamic Library,也就是会生成一个动态 Framework,我们能在 Products 下面看到每一个 Pod 对应生成的动态库。

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

参考

iOS 静态库和动态库 初识iOS中的动态库和静态库

Last updated