Frameworks
本文为译文,并已取得作者Nick Teissler同意。 原文链接:原文
本文章适合初学Framework的读者。
前言
Apple 已经将 iOS, macOS 的代码分成 Modules, libraries, frameworks。
Frameworks 的设计不单单只是为了封装资源跟模块化代码,更不只是为了减少代码的重编译时间而已。
要想减轻代码量、加速Debug、增加代码复用性,就不能只知道Framework是一个可以拖来拖去的工具箱,必须更近一步的了解这些:
- 静态库 - Static Libraries
- 动态库 - Dynamic Libraries
- Framework的结构
- Linking 链接 与 Embed 嵌入的不同
- Q & A
以下内容适用 macOS, tvOS, iOS. 可能会随时间有改动。
静态库 Static Libraries
在静态库之前,我们要先从Object File说起,以基础的角度来说,object files 就是个有结构的位元块。这些位元块包含着一些程序代码、有些包含着准备给Linker跟Loader使用的资料结构。
可以试着在终端输入以下指令,瞄一眼objectfile的样子
objdump -macho -section-headers /bin/ls
Objectfiles通常以四种形式登场
- Relocatable: 包含可以在编译时可被其他Relocatable Link的代码和数据,以共同制作成Executable。
- Executable:准备好载入记忆体的可执行档。
- Shared: 一种特殊的Relocatable file,概念类似 动态库。
- Bundle: 我们不特别描述Bundle, 在macOS上通常当作插件使用。
译文外补充: Relocatable:包含着能与其他relocatable在编译时进行Linked的代码(执行代码)以及数据(变量全局数据)。 Shared: 编译时只依Header作api上的确认,不包进任一Executable,共享于Executable之间。
若你尝试编译一段没有 main函数的 C 代码,会得到一个 relocatable object file
fancy.c
void fancySwap(int *xp, int *yp) {
if (xp == yp) return; // try it!
*xp = *xp ^ *yp;
*yp = *xp ^ *yp;
*xp = *xp ^ *yp;
}
在Terminal输入
cc -c fancy.c
产出的 fancy.o 是一种 objectFile ,包含着 fancySwap 符号以及对应的实现。为了更方便地处理 Relocatable object files,多个object files能被封装成 .a (a 代表着 archive) .a 档也被称作 静态库(static library) 或 静态归档(static archive)。
当executable中某function 或 数据来自于 .a,linker是能够智能地只链接其对应的符号,但是,linker还是会将所有的这些.a里的代码给一个固定的load地址并包含(copies and relocates)进executable中,造成肥大的executable,且增长读进memory的时间。
当你有10个应用使用此静态库,就需要个别有一份copy,也就是十个复本!而且若是静态库的开发者更新了代码,则所有的应用都必须重新编译一遍。而动态库就是为了解决这一步便利性而诞生的。
动态库 Dynamic Libraries
动态库 (也称作Shared Library, Shared object, 动态链接库), 跟静态库一样是多个object files封装起来的,不一样的是动态库只有在程序的载入时间(load time)或运行时(Run time)会被载入,并且在memory随机的配发一段地址。
以上行为是由动态链接器(Dynamic linker, macOS称dyld)来完成,动态库在:
- macOS上 以 .dylib 存在
- windows上 以 .DLL 存在
- linux上 以 .so 存在
然而在运行时进行才做链接其实是一个笨重的负担,应合理安排哪些库需要Load以及时机。
在 Treminal输入以下指令查看更多关于动态载入的内容
man dlopen
Shared object 解决了前面static object files的问题
- 现在多个executable只动态链接到 动态库的『唯一』拷贝。
- 更新库并不影响executable
- 库的依赖会被自动载入且链接(依赖库必须在search path里)
macOS 大规模的使用shared libraries,可以前往路径 /usr/lib文件夹查看系统的动态库。
如果你想要使用自己的动态库,必须确保它被 embedded 进你的App里。
看完上面这段,你可能会想 “太好了, 我不需要重编译了,但我最好为我库的使用者提供兼容性”。
是的没错,你的确需要考虑每当你update动态库时,API的兼容性,或许这也是你正在看这篇文章的原因.. 学习一种具有地址兼容性的bundling格式 - Framework
Framework
一个Framework其实就是一个有着特定结构的文件夹装着各种共享的资源。
这些资源通常是 图片、Xibs、动态库、静态库、文档...等,他们被封装成bundle存储却又不像其他的bundles( 像是 .app),Framework毫不掩饰的表明它纯粹就是一个文件夹。
这里指的Bundle并不是上面所提及的Bundle Object
Headers
这文件夹包含了Framework对外公开的C & Obj-C headers,Swift 并不会用到这些Headers,如果你的framework是用Swift写的,Xcode会自动帮你创建这个文件夹以提供互用性。
若你有时不清楚在Swift代码里加 @objc, @objcMembers 的影响,可以查看 Build Settings 里“SWIFT_OBJC_INTERFACE_HEADER_NAME”所指的文件,并试着改动@objc @objcMembers的宣告,看看这些改动对这文件的影响。
Modules
这文件夹包含了LLVM, Swift 的 Module信息。
.modulemap档案是给Clang使用的。
关于Clang 可看译者的另一篇文 LLVM前端Clang
.swiftmodule 文件夹下的档案类似headers,但是不像是 headers, 这些档案是二进制的且“无格式也有可能会改变”,在你Cmd-click 一个Swift函数时Xcode就是利用这些档案去定位其所属的module。
尽管这些都是二进制文件,但他们仍是一种叫 llvm bitcode 的结构,正因如此,我们能用llvm-bcanalyzer
and llvm-strings
取得相关信息。
MyCustomFramework
虽然他被finder标注成Unix executable,但他其实是一个 relocatable shared object file
Resources
本地化的资源, xibs, 档案, 图片... 跟其它资源存放在这
图片里的箭头 (Framework版本)
这些箭头都是 symlink 符号连结(捷径)。
每当产生一个新版本时,会被放进 Versions/B
. Current 的箭头会被更新成指向 B。
与此同时,原本动态链接到A的程序仍会保持链接A而不是Current,怎么办到的?
每当executable 在编译时, dynamic linker会纪录与自己有兼容的Framework路径。
But
现今Apple已鲜少使用这个功能了
Xcode 里 Linking vs. Embedding Frameworks
这里我们来讨论Xcode里一些常见的设定,Target的 general settings.
通常需要使用Framework时,都会选择 “Linked Frameworks and Libraries”,除非你打算在 runtime的时候才做链接跟载入(前面提及的dlopen)。
Linking 和 Embedding 的差别是什么?
为什么当你Embed一个Framework时,Xcode自动将Framework加到Link区?
想一下刚刚在读过的,dylibs储存在哪?当你想要用自己的dylib时该做什么?
是的!Embedding 实际上将Framework拷贝一份到你的application bundle里Frameworks文件夹下,
系统库是 iOS 和 macOS 自带的,你可以放心的链接他们,但是你自己的Framewroks并不是系统自带的,因此你必须要embedded 到 application bundle里。
而你的应用若是没有Linking它也没有任何用,所以Xcode自动帮你做了链接。
★★ Linking and embedding间接的暗示 动态 或是 静态链接,
我们现在知道embedding一个静态库是不合理的,因为静态库的符号已经被编译进executable,所以 Xcode不会应让你将static library放到 Embed里
(但其实Xcode会让你放,然而这是很没效率的使用,所以你不该这么做)
技术上来讲,macOS是可以让你在runtime时去载入一个静态的 .a file的,并标记一块记忆体当作是 executable,
这样做能有效的载入静态库,然而这技术是由 JIT compilers完成的,iOS并没有标记记忆体的能力。
★★
听说你想要使用 Framework
在你使用Framework或是将自己的代码做成Framework时,有哪些因素要考虑的?
- 你要开发一组应用吗?如果是,这就是Framework本质的一部分 - 分享 Frameworks,你需要去建一个包安装器把 shared frameworks放进
/Library/Frameworks
or~/Library/Frameworks
. - 你的多个apps共享着些许的重复代码吗?使用framework或许不是最好的选择,如果只是要共享一个非常小量的代码,为了保持同步的 打包和版本控制反而会成为负担。
- 你想试着模块化你的代码吗?如果你想要将代码移进Framework并将它变成模组,最好注意 Swift’s access control.
- 别将Framework当作能不重新编译的万灵丹,有些项目会将代码中极大量快完成的部分移动到Framework以提升编译速度,这样做的确能降低时间,如我们所知Framework不需重新编译,然而这样的方式会使模块更复杂,Debug, 测试难度提升。
- 你要将代码做成第三方包给其他人使用吗?这情况下,你可以选择使用动态库 静态库 或Framework。macOS, iOS已逐渐习惯动态Framewrok了,因为他的轻便性、普遍以及易用性。
Sources
- John Levine’s “Linkers and Loaders” www.iecc.com/linker/
- Bryant, R., OHallaron, D. R., & S., M. (2016). Computer systems: a programmers perspective. Harlow, Essex: Pearson.
- “[swift-users] Where to read about *.swiftmodule file format?” lists.swift.org/pipermail/s…
- “Dynamic-Library Programming Topics: Run Path Libraries” developer.apple.com/library/con…
- “Pew Pew the Spells: Static and Dynamic Libraries” pewpewthespells.com/blog/static…
- “Dynamic Library Programming Topics: Using Dynamic Libraries” developer.apple.com/library/con…
- “Apple Technical Note: Embedding Frameworks in an App” developer.apple.com/library/con…
- “Swift and Objective-C in the Same Project” developer.apple.com/library/con…
- “Framework Programming Guide: Installing Your Framework” developer.apple.com/library/con…