前言
文章比较长,所以在文章的开头我打算简单介绍一下这篇文章将要讲述的内容,读者可以选择通篇细度,也可以直接找到自己感兴趣的部分。
既然是谈 Cocoapods,那首先要搞明白它出现的背景。有经验的开发者都知道 Cocoapods 在实际使用中,经常遇到各种问题,存在一定的使用成本,因此衡量 Cocoapods 的成本和收益就显得很关键。
Cocoapods 的本质是一套自动化工具。那么了解自动化流程背后的原理就很重要,如果我们能手动的模拟 Cocoapods 的流程,无论是对 Cocoapods 还是 Xcode 工程配置的学习都大有裨益。比如之前曾经和同事研究过静态库嵌套的问题,很遗憾当时没能解决,现在想来还是对相关知识理解还不够到位。这一部分主要是介绍 Xcode 的工程配置,以及 target/project/workspace 等名词的概念。
最后,我会结合实际的例子,谈谈如何发布自己的 Pod,提供给别人使用。算是对 Cocoapods 的实践总结。
由于实践性的操作比较多,我为本文制作了一个 demo,提交在 我的 Github: CocoaPodsDemo 上,感兴趣的读者可以下载下来,研究一下提交历史,或者自己操作一遍。友情提醒: 本文所涉及的静态库均为模拟器制作,请勿真机运行。
为什么要使用 Cocoapods
我们知道,再大的项目最初都是从 Xcode 提供的一个非常简单的工程模板慢慢演化来的。在项目的演化过程中,为了实现新的功能,不断有新的类被创建,新的代码被添加。不过除了自己添加代码,我们也经常会直接把第三方的开源代码导入到项目中,从而避免重复造轮子,节约开发时间。
直接把代码导入到项目中看起来很容易,但在实践过程中,会遇到诸多问题。这些问题会困扰代码的使用者,大大的增加了集成代码的难度。
使用者的困扰
最直接的问题就是代码的后续维护。假设代码的发布者在未来的某一天更新了代码,修复了一个重大 bug 或者提供了新的功能,那么使用者就很难集成这些变动。
代码有增有删,如果把代码编译成静态库再提供给使用者, 就可以省掉很多问题。然而如果这么做的话,就会遇到另一个经典的问题: “Other linker flag”。
举个例子来说,可以在 Demo 的 BSStaticLibraryOne
这个项目中看到,这个静态库一共有两个类,其中一个是拓展 Extension。项目编译后就会得到一个 .a
文件。
我们都知道静态库的格式可以是 .framework
,也可以是 .a
。如果深究的话,.a
文件可以理解为一种归档文件,或者说是压缩文件。其中存储的是经过编译的 .o
格式的目标文件。我们可以通过 ar -x
命令来证明这一点:
ar -x libBSStaticLibraryOne.a
需要提醒的一点是,光有 .a
文件还不够,我们还需要提供头文件给使用者导入。为了完成这一点,我们需要在项目的 Build Phases 中新增一个 Headers Phase,然后把需要对外暴露的头文件放到 Public 一栏中:
此时编译后的头文件会放在 .a
文件所在目录下,usr/local/include
目录中。
接下来打开 OtherLinkerFlag
这个壳工程,引入 .a
文件和头文件,运行程序,结果一定是:
-[BSStaticLibraryOne sayOtherThing]: unrecognized selector sent to instance xxx
这就是经典的 linker flag
问题。首先,我们知道 .a
其实是编译好的目标文件的集合,因此问题出在链接这一步,而非编译。Objective-C 在使用静态库时,需要知道哪些文件需要链接进来,它依据的就是之前图中所示的 __.SYMDEF SORTED
文件。
可惜的是,这个文件不会包含所有的 .o
目标文件,而只是包含了定义了类的目标文件。我们可以执行 cat __.SYMDEF\ SORTED
来验证一下,你会看到其中并没有拓展类的信息。这样一来,BSStaticLibraryOne+Extension.o
虽然存在,但是不被链接到最终的可执行文件中,从而导致了找不到方法的错误。
解决上述问题的方法是调用者在 Build Settings
中找到 other linker flag
,并写上 -ObjC
选项,这个选项会链接所有的目标文件。然而根据文档描述,如果静态库只有分类,而没有类, 即使加了 -ObjC
选项也会报错,应该使用 -force_load
参数。
由于第三方的代码使用分类几乎是必然事件,因此几乎每个使用者都要做如上配置,增加了复杂度和出错的几率。
除此以外,第三方的代码很有可能使用了系统的动态库。因此使用者还必须手动引入这些动态库(请记住这一点,静态库不支持递归引用,这是个很麻烦的事情,后面会介绍),我们以百度地图 SDK 的集成为例,读者可以自行对比手动导入和 Cocoapods 集成的步骤区别: 配置开发环境iOS SDK。
因此,我总结的使用 Cocoapods 的好处有如下几个:
- 避免直接导入文件的原始方式,方便后续代码升级
- 简化、自动化集成流程,避免不必要的配置
- 自动处理库的依赖关系
- 简化开发者发布代码流程
Cocoapods 工作原理
在我之前的一篇文章: