对于做 iOS 开发的朋友来说,Cocoapods 是一件不必可少的得利工具,它是一个管理第三方库,并且解决其依赖关系的工具,但是有很多朋友对其运作的机制知其然却不知其所以然。笔者就在这里简单的讲解一下。
对于任何一项编程来说,早期未形成工程化的时候,是不存在所谓依赖管理的概念的,iOS 也是一样,早期 iOS 开发如果想要使用第三方库,是非常繁琐的一件事情。
将第三方库源代码复制到工程目录,或者使用 git submodule
将其作为项目的一个模块
设置工程文件,添加第三方库依赖的系统库
对于某些第三方库,可能需要设置一些编译选项( etc. -fno-objc-arc
)
最后就是管理第三方库代码的更新
这些都是体力活,为了能省去这些工作,有没有办法呢?答案是有的,iOS 开发终究还是很传统的 Unix 开发,直接把第三方库源代码抽离出来,打包成静态库就行了(PS:在 iOS 8 之前苹果只允许使用静态库,而 iOS 8 后就可以打包动态库了,当然,实际上是一样的。)。这样的话就不需要担心过多的源代码导入的繁琐,也不需要担心第三方库究竟需不需要编译选项,而且第三方库更新只需要更新静态库就行了。简直是美好的人生。
封装成静态库或动态库看起来确实美好了,方便快捷,这也是 Unix 下的解决方案。但是只要仔细一想,问题一大堆。
某些第三方库很可能直接依赖另一项第三方库,依赖如果使用手工解决将会是很大的工作。
动态库不能用于 iOS 7 及以下版本
工程依旧需要设置依赖的系统库
仍然需要手工更新第三方库版本
对于 Unix 环境来说,动态库可以存放在系统默认搜索路径下,这样所有的应用都可以共享同一个内存副本,而且升级动态库的时候可以一起升级,不需要到各处寻找。但是 iOS 的动态库实际上是缩水的,因为苹果将动态库限制在了沙盒内部,其他 App 完全不能访问此动态库。这就限制了动态库的优点。还有一点原因就是指令集架构的不同,iOS 模拟器使用的是 x86 架构指令集,而真机则是 ARM64 等指令集,如果想要方便使用,最好需要打包通用架构的静态库,但是这是很繁琐的工作。由于以上原因,手工封装静态库或者动态库实际上在项目小的时候是可以的,但是项目规模一旦扩大就会导致效率低下。
为了能够自动化流水作业,就引出了依赖包管理的概念,也就是 Cocoapods。Cocoapods 本质上还是上面所说的封装动态库静态库,但是它解决的最大问题就是依赖管理。开发者不需要从 Github 的地方辛苦的寻找代码,只需要一条命令,就能下载整合第三方库。这些优点都不必说,笔者下面就具体讲解 Cocoapods 到底对我们的工程做了什么事情。
首先使用 Xcode 创建了一个工程,使用 git init
命令将其纳入版本控制,做第一次提交。然后写 Podfile,直接跟随 Cocoapods 官网文档写最简单的内容。
platform :ios, '8.0' use_frameworks! target 'test' do pod 'AFNetworking', '~> 2.6' end
然后输入以下命令整合第三方库。
> pod install
现在可以看一下修改的内容。
> git status On branch master Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: test.xcodeproj/project.pbxproj Untracked files: (use "git add <file>..." to include in what will be committed) Podfile.lock Pods/ test.xcworkspace/
由于输出过场,这里就不贴 git diff
命令的输出了,从 git diff
命令输出可以看出,主要修改了 .xcodeproj/project.pbxproj
文件的内容,并且将原先的文件格式转化为了 XML 文件格式。并且,可以看出来,添加了一个 工程名.xcworkspace
的工作空间,将原先的工程文件和新的 Pods 工程放到了同一个工作空间下。
<?xml version="1.0" encoding="UTF-8"?> <Workspace version = "1.0"> <FileRef location = "group:test.xcodeproj"> </FileRef> <FileRef location = "group:Pods/Pods.xcodeproj"> </FileRef> </Workspace>
很显然,所有的配置都被放到了工程文件中,工作空间实际上只是一个工程的整合。由于文件格式不同,无法很好的对比反映工程文件到底哪里被修改了,但是从实际的工程打开后观察,也能稍微了解。总结如下:
在 General -> Linked Frameworks and Libraries 中添加了 Pods_test.framework 的依赖
Build Settings -> Other Linker Flags 中添加了 -framework "AFNetworking"
Build Settings -> RunPath Search Paths 中添加了 @executable_path/Frameworks
Build Settings -> Other C Flags 添加 -iquote "$CONFIGURATION_BUILD_DIR/AFNetworking.framework/Headers"
和 Other C++ Flags 添加 $(OTHER_CFLAGS)
,也就是说,跟随 Other C Flags 配置
Build Settings -> Preprocessor Macros 添加 COCOAPODS=1
Build Settings -> User-Defined -> MTL_ENABLE_DEBUG_INFO PODS_FRAMEWORK_BUILD_PATH PODS_ROOT 三个变量
Build Phases -> Embed Pods Frameworks 添加 "${SRCROOT}/Pods/Target Support Files/Pods-test/Pods-test-frameworks.sh"
Build Phases -> Copy Pods Resources 添加 "${SRCROOT}/Pods/Target Support Files/Pods-test/Pods-test-resources.sh"
从上面可以看出,如果在 Podfile 中指定了 use_frameworks!
则会使用动态库封装其内容,而上面所做的主要工作就是添加对另一个工程的依赖,添加链接器选项使其链接 framework,将动态库运行时搜索路径设置为 @executable_path/Frameworks
也就是说 ${BUNDLE_ROOT}/Frameworks
,设置 C 和 C++ 编译器的头文件搜索路径,将 framework 放置到沙盒目录
然后再来看 Pods 工程,Pods 工程将每个第三方库都使用一个 target 表示,还有一个 target 则是用于生成主要的库文件,需要注意的是,虽然第三方库的 target 生成的是动态库,但是最终的 target 生成的是静态库,但是只是起一个集合的作用,实际的二进制代码还是被存放在各个动态库中,其主要修改了 Build Settings 内容
${SRCROOT}/../build iOS Deployment Target
主要就是这两点,其他都是一些细碎的细节,比如将输出类型指定为动态库,设置第三方库依赖。
然后就是 Pods-工程名-frameworks.sh
和 Pods-工程名-resources.sh
,这两个文件实际上就是将封装通用架构动态库文件和将动态库复制到 bundle 指定目录下。有兴趣的朋友可以仔细看看。
framework 只能在 iOS 8 以上平台使用,但是大多数情况下,工程都是需要兼容 iOS 7 版本的,所以 framework 功能只能舍弃掉,因为目前兼容版本都是以 UI 特征来划分兼容版本,iOS 6 及以前版本都算拟物化 UI,而 iOS 7 及以后版本则是扁平化风格。
这里先将 Podfile 中的 use_frameworks!
删除掉,再 pod install
。
通过 git diff
命令可以看到,实际上两种方式差别不大,只是不使用动态库而是使用静态库替代了所有的动态库,然后在 Other Linker Flags 中添加了 -ObjC
和链接器命令链接所依赖的系统 framework,原先则是在 Pods 工程具体的 target 中才存在链接器命令链接系统 framework。而且还发现,在 Header Search Path
中添加了头文件的搜索路径。
Cocoapods 虽然侵入性很强,但是对于新手来说,屏蔽了很多底层的操作,非常便于实践,对于重复的工作也得到了很好的替代,如果有朋友想要补充,可以在下方留言指正。