Cocopods 使用
开篇叨叨
一般一款软件都往往由几个人同时协作开发,并且开发期间会使用多个开源库,从而避免重复造轮子,这样可以加快开发的速度,但是这也带来了一个问题:如何管理这些依赖,这是一个十分浪费时间并且容易出错的工作,我们不但要定期管理这些三方库的更新,还需要往目标项目工程上添加三方库所需要的依赖库,对于某些开源库可能还需要添加某些编译参数。稍稍完备一点的编程语言一般都会引入依赖管理工具,来减轻这些负担。比如Java语言的Maven,Nodejs的npm,Android的Gradle,Ruby的gem。iOS也有它的依赖管理工具 — CocoaPods。
CocoaPods 安装
- CocoaPods 是使用Ruby 实现的,它使用gem命令进行下载并安装,在安装前最好使用下面的命令来更新下gem
sudo gem update --system |
- gem是从对应的Ruby软件库中下载对应的软件的,默认情况下使用的是https://rubygems.org它托管在亚马逊云服务上,国内可能会被墙,所以一般会使用国内的源来替换,如果你遇到了这个问题可以使用下面的命令来替换Ruby软件库:
gem sources -l //查看Ruby数据源 |
- 安装最新版本的CocoaPods
sudo gem install cocoapods |
- 安装指定版本的CocoaPods
sudo gem install cocoapods -v 1.4.0 |
- 查看当前本地安装的CocoaPods版本
gem list cocoapods |
- 卸载当前安装的CocoaPods
sudo gem uninstall cocoapods |
有时候卸载不干净可以通过上面的gem list cocoapods列出所有相关的库,并通过下面命令卸载
gem uninstall cocoapods |
- 初始化CocoaPods repo
pod setup |
这一步是比较耗时的,它是将 pod repo 的 镜像索引信息下载到 ~/.cocoapods/repos目录下,如果这个进度实在等得太久了可以试着 cd 到那个目录,用du -sh *来查看下载进度,我们怎么知道我们有哪些repo呢?
可以使用pod repo命令来查看:
master |
URL表示远程的repo库地址,Path表示下载存放的地址。Type 是代码库类型,上图表示的是从https://github.com/CocoaPods/Specs.git这个地址,将镜像索引下载到本地的/Users/huya/.cocoapods/repos/master 文件夹下。
镜像索引其实是一个配置文件,里面包含了某个库当前的版本,这些库的地址,如下图所示:
我们可以手动添加删除pod repo库
pod repo remove master |
当然可以通过在Podfile中通过source来添加,这个后面会举例说明。
一旦setup之后,后续如果需要更新repo库可以通过来更新。
pod repo update |
CocoaPods 常用命令行
- 初始化Pod
pod init |
这时候会在当前目录下新创建一个Podfile,后面将专门介绍如何编写Podfile
- 安装依赖
pod install |
这时候会参照Podfile从~/.cocoapods/repos/对应的目录下寻找对应库的下载地址并将库下载到Pod文件夹下,然后生成对应的workspace文件,这个在后面介绍原理的时候再详细介绍
- 查找对应的pod库
pod search |
如果只是开发的话一般上面几个命令就够用了
- 使用指定版本执行命令
pod 1.4.0 install |
- install 的时候输出详细过程Log
pod install –verbose |
- 更新某一个组件
// 不添加组件名则更新所有 |
- 更新本地依赖
如果 github 或者私有仓库上面有最新版本,本地搜到的还是旧版本。如果 Podfile
中使用新的版本号,这样是无法执行成功的,这时候必须对本地依赖库进行一次更新。
pod repo update |
多版本CocoaPods管理
有时候会遇到比如工作中我们协商好都用1.4.0 但是我们自己的某些练习项目需要用到1.8.4 这时候是最头疼的一件事情,为了解决这个问题我目前使用Bundler来管理各个项目的CocoaPods版本:
安装bundler:
gem install bundler |
和Cocoapods的Podfile文件一样,我们需要创建一个Gemfile文件,文件位置和Podifle所在位置相同即可,可以使用:
bundle init |
在Gemfile文件中,配置所需的Cocoapods版本:
source "https://rubygems.org" |
执行bundle install
之后就可以在相应位置,执行bundle exec pod xxxxx 就可以了。
****Podfile ****
最全面的还是官方文档:Podfile 官方文档
一个比较简单的Podfile如下所示
source 'https://github.com/CocoaPods/Specs.git' |
1. install
这个命令是cocoapods声明的一个安装命令,用于安装引入Podfile里面的依赖库,也就是pod install执行时的一些参数设置,在项目中没有使用过,在这里先不做过多介绍,后面如果有机会会带大家过一下CocoaPods源码,到时候再看下这个配置的作用。
2. source
source 用于指定specs的位置,sources的顺序是有关系的。CocoaPods将使用pod第一次出现的source中的最高版本.cocoapods 官方source是隐式的需要的,如果只有一个cocoapods官方的source则可以省去不写,但是一旦你指定了其他source 你就需要也把官方的指定上。
source 'https://github.com/CocoaPods/Specs.git' |
3. target
target指明当前的依赖是针对哪个项目target的,可以在target块里面为某个指定的target定义依赖项,一般我们会通过def定义一个依赖集合,然后在不同的target引用,
def common_Pods |
4. platform
platform指定了静态库应该被编译在哪个平台.下面是各个平台platform的默认值。
iOS -> 4.3 |
5. inhibit_all_warnings
inhibit_all_warnings! 用于屏蔽cocoapods库里面的所有警告,它也可以用于屏蔽某个库的编译警告
pod 'SSZipArchive', :inhibit_warnings => true |
6. use_frameworks
use_frameworks用于告诉CocoaPods我们想使用Frameworks而不是Static Libraries。不显式指定的话会默认使用Static Libraries,会在Pods工程下的Products目录下生成.a的静态库,如果指定的话会在Pods工程下的Frameworks目录下生成依赖库的framework,由于Swift不支持静态库,所以如果项目中使用到Swift库的话就必须使用use_frameworks!,纯OC项目是不用use_frameworks的。
7. Pod
指定每个target的依赖项
- 如果后面不写依赖库的具体版本号,那么cocoapods会默认选取最新版本,一般不推荐使用这种方式,因为库升级的时候可能会带来不兼容或者其他怪异的问题,一般我们项目中都不会使用这种方式,而是在使用库的时候明确指明库对应的版本,如果要升级库也要我们明确,库的升级是否会给我们带来问题,并通过测试后才能升级发布,否则一个项目中有几十个库,很难追踪问题是哪个库升级导致的。
pod 'SSZipArchive' |
- 要特定的依赖库的版本,只需要在后面写上具体版本号即可:
pod 'Objection', '0.9' |
- 当然除了指定特定版本外还可以通过下面的来指定版本范围,但是个人还是推荐明确指明所依赖的库的版本。
* > 0.1 高于0.1版本(不包含0.1版本)的任意一个版本 |
- pod 中还可以指定当前依赖只在某些给定的build configuration中被启用,比如:
pod 'LookinServer', :configurations => ['Debug'] |
- 正常情况下我们会通过依赖库的名称来引入,但是有些依赖库是有多个子依赖的,比如:
pod 'Firebase/Analytics' |
这种除了使用上面写法外,还可以通过Subspecs来代替,但是个人还是推荐上面的写法比较简单:
pod 'Firebase', :subspecs => ['RemoteConfig'] |
- 使用本地依赖
pod 'AFNetworking', :path => '~/Documents/AFNetworking' |
- 使用指定远程依赖
引入master分支(默认)
pod 'AFNetworking', :git => 'https://github.com/gowalla/AFNetworking.git' |
引入指定的分支
pod 'AFNetworking', :git => 'https://github.com/gowalla/AFNetworking.git', :branch => 'dev' |
引入某个Tag标签的代码
pod 'AFNetworking', :git => 'https://github.com/gowalla/AFNetworking.git', :tag => '0.7.0' |
引入某个特殊的提交节点
pod 'AFNetworking', :git => 'https://github.com/gowalla/AFNetworking.git', :commit => '082f8319af' |
- 从另一个源库引入
pod 'PonyDebugger', :source => 'https://github.com/CocoaPods/Specs.git' |
pod 'JSONKit', :podspec => 'https://example.com/JSONKit.podspec' |
podspec 用于指定podspec的信息:
# 不指定表示使用根目录下的podspec,默认一般都会放在根目录下,并且使用库名作为名称 |
8. project
用于指定当前target要应用到哪个project,这个用在多project的情况下,如果没有指定说明该target要应用到Podfile目录下与target同名的工程
# MusicApp这个target只有在MyMusic工程中才会链接 |
9. workspace
默认情况下不需要指定,直接使用与Podfile所在目录的工程名一样就可以了。如果要指定另外的名称,而不是使用工程的名称,可以使用workspace来指定:
workspace 'MyWorkspace' |
10. def
def命令来声明一个pod集, 然后在需要引入的target中引入:
def common_Pods |
11. pre_install
这个允许用户在Pods下载完成,但还未安装前对Pods做一些修改,它有一个唯一参数Pod::Installer
pre_install do |installer| |
12. post_install
当我们安装完成,但是生成的工程还没有写入磁盘之时,我们可以指定要执行的操作。比如,我们可以在写入磁盘之前,修改一些工程的配置:
这两个在项目中暂时没有遇到过。
post_install do |installer| installer.pods_project.targets.each do |target| |
下面是一个比较全的一个Podfile模版,供大家参考:
source 'https://github.com/CocoaPods/Specs.git' # 组件依赖文件所存放仓库,根据需求可引入多个 |
CocoaPods 工作原理
关于CocoaPods的工作原理的细节大家可以翻看下CocoaPods的源码看下整个流程:
下面推荐几篇CocoaPod源码解析文章给大家,大家可以结合这些文章对源码进行解析:
这里只想从使用者的角度来介绍下CocoaPods工作原理,主要包括
- CocoaPods的组成
- CocoaPods的工作流
- pod install,pod update,Podfile.lock,Manifest.lock
- CocoaPods的组成
CocoaPods 本身是由Ruby编写的,它是由多个Ruby包组成的,最主要包括CocoaPods Core,CocoaPods Downloader,Xcodeproj,CLAide,Molinillo这些Ruby包,这些包是通过gem进行管理的,当我们执行pod 命令的时候就会调用对应的模块完成对应的任务,下面是对应模块的源码地址,作用,以及对应的帮助文档,大家在遇到问题的时候可以查找这些资料来解决问题。
模块 | 说明 | 帮助文档 |
---|---|---|
CocoaPods/Specs | 三方库Podspec文件托管仓库 | Doc |
CocoaPods | CocoaPod 命令行工具 | Guide |
CocoaPods Core | CocoaPods 相关文件(Specification,Podfile,Source)的处理 | Guide |
CocoaPods Downloader | 支持git/SVN/等多种协议的代码下载器 | Guide |
Xcodeproj | 创建和修改Xcode projects文件 | Guide |
CLAide | 命令行接口框架 | Guide |
Molinillo | 通用依赖解析器 | Guide |
- CocoaPods的工作流
整个CocoaPods涉及到了三方组件库的开发者,CocoaPods Repo,三方组件库的使用者,三方面对象,整个关系如下图所示:
首先三方组件库的开发者编写完三方库的代码后会将组件代码上传到GitHub/SVN等代码托管仓库,然后创建一个 podspec 文件,该文件包含了该组件包含的代码及资源,以及依赖关系,版本信息,以及该组件的存储地址,然后将这个podspec推送到CocoaPods共有仓库或者私有Spec管理仓库。
在运行pod setup或者pod install的时候会查看source指令,将source指令所指定的repo 下载到
/.cocoapods/repos目录下。这个文件夹下包含了各个三方组件对应版本以及该版本的podspec文件和podspec.json文件,后续需要下载的时候就可以根据podspec中指定的下载路径下载三方组件了。如果有一个第三方库发布了一个最新的版本,如果不执行pod repo update,那么本地是不会知道有一个最新版本的,还一直以本地的资源目录为准。那么我们永远都拿不到这个库的最新版本,但是有时候我们不执行pod repo update发现也可以拿到最新的库,那是因为pod update会先拉取远程最新目录,再根据目录中的资源重新更新一遍pod,但是如果podfile没有为每个库指定明确的版本,那么每次都会拉取一遍最新库,这时候如果不想每次都拉取,可以使用pod update –no-repo-update。正常情况下pod repo update 会将/.cocoapods/repos/下的所有组件库都更新一遍,如果只想更新某个私有库那么只需要带上需要更新的文件夹就可以只更新某个repo了。pod repo update ~/.cocoapods/repos/XXX/
三方组件库使用者要使用某个组件,需要在Podfile中指定组件名字,版本,repo 源,然后运行pod install命令,CocoaPods 会首先使用eval运行Podfile,并开始解析Podfile。
pod install可以分成如下阶段:
prepare 准备阶段
在该阶段首先会先检查当前运行目录是否是项目根目录,为啥要在根目录?因为pod init的时候需要从.xcodeproj中获取target信息,这就导致了生成的Podfile在项目根目录,而pod install需要Podfile所以也必须需要在根目录。接着检查Podfile.lock文件cocoapods和当前的cocoapods版本是否一致,Podfile中的plugin插件是否已经安装完成并加载,一旦这些检查完毕就会创建Pods以及子目录,并运行pre_install。resolve_dependencies 解决依赖冲突
这个阶段主要是解析podfile文件中的pod 以及 target等信息以及之间的关系,存储到对应的数据结构中。如果Podfile中有删除的库, 先进行文件清理。download_dependencies 下载依赖
这个阶段会拿着组件名,组件版本到~/.cocoapods/repos/中去寻找对应的podspec,在podspec文件中找到三方组件存放的地址,从而从远程下载。当然不是每个都需要下载,如果某个组件已经下载,并且版本没有变化的情况下就不会从远程下载。validate_targets target校验
这阶段将会对下载的依赖进行校验,比如校验是否有多重引用framework 或者 library 的情况,检查不同target所使用的swift版本是否相同,如果使用swift的情况下,检查Podfile是否添加了use_frameworks!。generate_pods_project 生成 Pod project 文件
这阶段将会生成Pods.xcodeproj工程文件,并将下载的依赖文件,Library 加入工程,处理 target 依赖,并将项目文件以及Pods项目文件添加到新生成的.xcworkspace文件。pod install,pod update,Podfile.lock,Manifest.lock
其实上面已经对这部分内容有所介绍了,这里将这些内容放在一起对比下会更加明显:
在项目第一次使用Cocoapods时候,或者在podfile文件中增加或者删除某个组件的时候需要使用pod install而不是pod update。
当运行pod install,它只解析Podfile.lock中没有的pod的依赖库.对于Podfile.lock中已经有的组件库, Podfile.lock不会尝试检查~/.cocoapods/repos是否有更新的版本.对于没有在Podfile.lock中列出的组件库,pod会搜索与Podfile匹配的版本或最新的版本,并将每个组件已经安装的版本写入到Podfile.lock中.也就是说Podfile.lock 的功能是用于跟踪每个组件的已安装版本并锁定这些版本。
简单说:pod install会优先考虑Podfile里指定的版本信息,其次考虑Podfile.lock 里指定的版本信息来安装对应的依赖库,而不会每次都考虑~/.cocoapods/repos的最新版本
当运行pod update的时候CocoaPods将尝试查找更新的组件版本, 并且会忽略掉Podfile.lock中已经存在的版本.
在多人协作的项目中一般需要将Podfile.lock文件提交到版本控制库中,这样大家就会保证同一时刻使用的三方组件库是一致的,只有在确认某个更新需要同步的时候,某人运行pod update后将Podfile.lock再次提交到代码仓库,其他人拉取到这个最新的Podfile.lock的时候就会提示需要更新,这时候其他人就也需要使用pod update 更新本地的三方组件,但是不论怎样,整个团队的三方组件库总是一致的。
上面介绍了Podfile.lock 那么 Manifest.lock 的作用又是什么?Manifest.lock 是 Podfile.lock 的副本,每次只要生成 Podfile.lock 时就会生成一个一样的 Manifest.lock 存储在 Pods 文件夹下。在每次项目 Build 的时候,会跑一下脚本检查一下 Podfile.lock 和 Manifest.lock 是否一致。也就是说Manifest.lock是用于记录本地/Pod目录下各个组件库的版本信息,那不是和Podfile.lock重复了么?不是的,因为一般我们会将Podfile.lock放到代码仓库中,这部分是有可能其他人远程改动后,我们拉下来,这时候有可能导致Podfile.lock中指定的组件版本和本地的组件版本不一致,那么我们怎么发现这种不一致呢?靠Podfile?这是不可能的,一般就是因为Podfile改了,pod update后导致Podfile.lock发生改动,这样如果没有Manifest.lock 就难以判断远程的是否和本地的版本有差异了,也就是说Manifest.lock 用于标记本地sandbox中三方组件的版本,这也是为什么有了Podfile.lock之后还需要Manifest.lock 的原因了。
CocoaPods 私有库/共有库 制作依赖库
1 创建组件库工程
将代码clone到本地,在根目录运行:
pod lib create IDLUtils |
会自动引导我们创建一个组件库:
这时候会生成IDLUtils.podspec文件,文件内容如下:
# |
下面是pod spec create 创建的大家可以对比下,这种方式生成的选项更多,但是pod lib create 对于简单的组件已经够用了。
# |
1 创建GitHub 代码仓库
这里需要创建两个GitHub库,一个是Spec仓库,一个是组件代码仓库,我们这里先介绍组件代码仓库,最后的时候会给大家介绍Spec仓库:
首先创建一个代码仓库用于存放组件代码,如下所示:
https://github.com/tbfungeek/IDLUtils.git
git add . |
将组件代码push到远端仓库,新建tag
git tag 0.0.1 |
将本地的tag推送到远程仓库
git push --tags |
注意tag号要和s.version保持一致
在上面工作完成后运行pod lib lint IDLUtils.podspec对podspec文件进行校验。如果遇到有问题可以通过****–verbose****来看详细的过程。
下面是可能会遇到的导致校验不过的可能问题:
情景一:
[!] IDLUtils did not pass validation, due to 1 warning (but you can use `--allow-warnings` to ignore it). |
这种情况下通过****–verbose**** 看下如果warning没问题可以通过****–allow-warnings**** 忽略错误。
情景二:
.podspec error - source_files` pattern did not match any file |
这种一般是只是将文件放置到Class目录,没有将Class添加到XCode的引用关系中。
情景三:
Could not find a `ios` simulator, Ensure that Xcode -> Window -> Devices has at least on |
升级cocoaPods版本。
情景四:
[!] Found multiple specifications |
将私有仓库拉到本地时可能会存在两个。移除重复的库。
有时候你会发现什么都对,但是结果不对,你就可以进入对应的缓存中查看下实际的内容是怎样的?或者使用下面的命令清除缓存看下:
rm ~/Library/Caches/CocoaPods/search_index.json |
3 注册CocoaPods
pod trunk register tbfungeek@163.com 'tbfungeek' --description='tbfungeek' |
这时候注册邮箱会收到一份验证邮件,通过邮件链接可以完成注册
4 将podspec push 到 CocoaPods Specs
pod trunk push IDLUtils.podspec |
创建私有Spec 仓库
1.新建私有仓库
目前github也支持私有仓库了,所以可以在github,gitlab上创建一个。
https://github.com/tbfungeek/IDLPodSpecs.git |
2.将私有仓库添加到本地
pod repo add IDLPodSpecs https://github.com/tbfungeek/IDLPodSpecs.git |
这时候会将IDLPodSpecs clone 到 ~/.cocoapods/repos 目录
3. 提交 podspec 至私有 Spec 仓库
pod repo push IDLPodSpecs IDLUtils.podspec |
4. 在项目中应用该私有库
在Podfile 头部添加repo 源
source 'https://github.com/tbfungeek/IDLPodSpecs.git' |
接下来就可以使用了
更深入学习
学习了上面的技术够一般项目使用了,但是随着项目推进,你会发现项目编译速度会越来越慢,甚至达到难以容忍的地步,这时候就需要进行CocoaPods 的组件二进制化,这样就省去了各个组件库的编译速度。这个会在后续章节中专门开一篇博客进行介绍,大家如果感兴趣可以事先了解下,下面是一篇个人认为写得比较好的一篇博客,推荐给大家。