CocoaPods 是一个比较常用的 iOS 依赖管理工具,今天抽空做一个小结,记录一下在 Git 环境下如何使用 CocoaPods 创建、管理私有仓库。
创建私有仓库的主要步骤:
- 创建私有 Spec Repo;(从未创建过私有库时需要,只需创建一次)
- 创建 pod 及 Example 工程;(2 和 3 二选一)
- 创建 xxx.podspec 文件;(2 和 3 二选一)
- 本地调试 xxx.podspec 文件及 pod 库功能;
- 向私有的 Spec Repo 中提交 xxx.podspec;
- 使用创建好的 pod 库;
- 更新维护 xxx.podspec。
一、创建私有 Spec Repo
首先介绍一下 Spec Repo,它是所有 Pods 的一个索引,实际也是一个 Git 仓库,远端在 GitHub、其他代码托管平台或自家的服务器上,当使用了 CocoaPods 后会被 clone 到本地的 ~/.cocoapods/repos
目录下,进入此目录后看到的 master 文件夹就是官方的 Spec Repo,本文中自己创建的私有库会在与 master 平级的位置。master 目录的结构大概是这样的(此处缩减了几个层级):
.
├── CocoaPods-version.yml
├── README.md
└── Specs
├── [SPEC_NAME_A]
│ ├── [VERSION_1]
│ │ └── [SPEC_NAME_A].podspec
│ └── [VERSION_2]
│ └── [SPEC_NAME_A].podspec
├── [SPEC_NAME_B]
│ ├── [VERSION_1]
│ │ └── [SPEC_NAME_B].podspec
│ └── [VERSION_2]
│ └── [SPEC_NAME_B].podspec
└── [SPEC_NAME_C]
└── [VERSION_1]
└── [SPEC_NAME_C].podspec
注意:目录结构是用
tree -L [level]
命令打印的, tree 可以通过 Homebrew 的命令brew install tree
安装的。
我们需要创建一个类似于 master 的私有 Spec Repo,这里自己创建一个 Git 仓库,这个仓库可以创建私有的,也可以创建公开的(此处创建了公开的),对于私有仓库,如果项目中有其他同事共同开发的话,你还要给他这个 Git 仓库的权限。这里我使用了 码云。
在服务端创建,创建完成之后在 Terminal 中执行如下命令:
# pod repo add [Private Repo Name] [remote repository clone URL]
$ pod repo add riversea2015_mobile_iphone_repo_local https://gitee.com/riversea2015/riversea2015_mobile_iphone_repo.git
至此第一步创建私有 Spec Repo 完成。
注意:
1:riversea2015_mobile_iphone_repo_local 是本地显示的名字,可以和后边链接里的 riversea2015_mobile_iphone_repo 名称不同(⊙o⊙)
2:如果有其他合作人员共同使用这个私有 Spec Repo 的话,在他有对应 Git 仓库的权限的前提下,执行相同的命令添加这个 Spec Repo 即可。
3:如果有现成的组件项目,并且在 Git 的版本管理下,那么可以直接进行第 3 步了;如果你的组件还在你冗余庞大的项目中,需要拆分出来或者需要自己从零开始创建一个组件库,建议使用 CocoaPods 提供的工具创建 pod 及 Example 工程,详见第 2 步。
二、创建 pod 及 Example 工程
2.1 创建 pod + Example
此处使用 CocoaPods 提供的一个工具创建单独的 pod 项目文件,工具的文档介绍是 Using Pod Lib Create, 就拿我创建的 HHPodTestLibrary 为例介绍一下具体操作:
# 1.进入要创建项目的目录
$ cd /Users/sea/Documents
# 2.创建 pod(附带 Example)
# pod lib create ProjectName --template-url=https://github.com/CocoaPods/pod-template.git // 这是完整命令
$ pod lib create HHPodTestLibrary
# 接下来会提示配置以下 6 项基础数据,“>” 后是我的选择:
# 选择平台
What platform do you want to use?? [ iOS / macOS ]
> ios
# 选择编程语言
What language do you want to use?? [ Swift / ObjC ]
> objc
# 是否需要一个例子工程
Would you like to include a demo application with your library? [ Yes / No ]
> yes
# 选择一个测试框架
Which testing frameworks will you use? [ Specta / Kiwi / None ]
> specta
# 是否要做 UI 测试
Would you like to do view based testing? [ Yes / No ]
> yes
# 定义类前缀
What is your class prefix?
> HH
执行完以上操作,就会自动执行 pod install
命令以创建项目并生成依赖,然后会自动使用 Xcode 打开 Example 工程。
2.2 添加库文件,并提交远端仓库
首先在 Pod 的 Classes 中添加了 4 个文件,然后进入 Example 文件夹执行 pod update
命令,再打开项目工程可以看到,刚刚添加的组件已经在 Pods 子工程下的 Development Pods/HHPodTestLibrary
中了。
测试无误后需要将该项目添加并推送到远端仓库。
通过 CocoaPods 创建出来的目录本身就在本地的 Git 管理下,我们需要做的就是给它添加远端仓库,同样去 GitHub 或其他的 Git 服务提供商那里创建一个私有的仓库,拿到 SSH 或 HTTPS 地址,然后 cd 到 PodTestLibrary 目录,提交修改:
$ git add .
$ git commit -s -m "Initial Commit of Library"
$ git remote add origin https://gitee.com/riversea2015/HHPodTestLibrary.git # 添加远端仓库(SSH或Https)
$ git push origin master # 提交到远端仓库
注意:
1.如果一开始在远端创建的仓库里有文件,执行
git push origin master
的时候是推不上去的,应该会提示你执行git pull origin master
命令拉取并合并远端代码,不过,当你执行此命令时,又会报下边的错:
fatal: refusing to merge unrelated histories
此时,可以执行
git pull origin master --allow-unrelated-histories
命令强制合并 2 个不相干的 git 仓库,然后使用正常的git push origin master
就可以了推上去了。
其实,还有一种更加简便也更加合理的方式解决这个问题,那就是在远端创建仓库时一个文件都不要加,如下图:
然后在终端已经存在的 git 仓库中执行以下操作(上图虚线框中的命令)就可以推上去了:
bogon:untitled sea$ git remote add origin https://github.com/riversea2015/test.git
bogon:untitled sea$ git push -u origin master
2.建议将整个目录 (pod 和 Example) 上传到私有仓库,构成一个完整的工程,这样后期为 pod 添加新功能的时候,就可以顺便在 Example 里边进行验证。
3.每当你向 Pod 中添加了新的文件或者以后更新了 xxx.podspec 中的版本号时,都需要重新执行一遍
pod update
命令。
2.3 配置/修改 xxx.podspec 文件
执行完上边的操作之后,就该编辑 HHPodTestLibrary.podspec 文件了,这是一个 Ruby 的文件,把编辑器的格式改成 Ruby,就能看到语法高亮,下面我贴上我的 podspec 文件,并在后面以注释的形式说明每个字段的含义,没有涉及到的字段可以去官方文档查阅。(此处我的文件没有删除注释,实际操作时可以删掉注释)
#
# Be sure to run `pod lib lint HHPodTestLibrary.podspec' to ensure this is a
# valid spec before submitting.
#
# Any lines starting with a # are optional, but their use is encouraged
# To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html
#
Pod::Spec.new do |s|
s.name = 'HHPodTestLibrary' # 名称
s.version = '0.1.0' # 版本号
s.summary = 'This is the first pod repo test library.' # 简介
s.description = 'This is the first pod repo test library!' # 详细介绍
s.homepage = 'https://gitee.com/riversea2015/HHPodTestLibrary' # 主页,尽量填写可以访问到的地址,否则验证不通过
# s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2' # 屏幕截图
s.license = { :type => 'MIT', :file => 'LICENSE' } # 主页,这里要填写可以访问到的地址,不然验证不通过
s.author = { 'hehai' => 'hehai682@126.com' } # 作者信息
s.source = { :git => 'https://gitee.com/riversea2015/HHPodTestLibrary.git', :tag => s.version.to_s } # 项目地址,这里不支持 ssh 的地址,验证不通过,只支持 HTTP 和 HTTPS,最好使用 HTTPS
# s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>' # 多媒体介绍地址
s.ios.deployment_target = '8.0' # 支持的平台版本,此处是 iOS8.0 以上
s.requires_arc = true # 是否使用 ARC,如果指定具体文件,则具体的问题使用 ARC
s.source_files = 'HHPodTestLibrary/Classes/**/*.{h,m}' # 文件路径
# s.resource_bundles = {
# 'HHPodTestLibrary' => ['HHPodTestLibrary/Assets/*.png'] # 资源文件地址
# }
s.public_header_files = 'HHPodTestLibrary/Classes/**/*.h' # 公开的头文件地址
s.prefix_header_contents = '#ifdef __OBJC__','#import "其他pod中的头文件A.h"','#import "其他pod中的头文件B.h"','#import "其他pod中的头文件C.h"','#endif' # 相当于 pod 中的一个 pch 文件,可以保证等号右边的头文件会被此 podspec 文件对应的库中所有文件可见,而其中的 #ifdef __OBJC__ 保证只在 OC 环境中引入这些头文件,其它如 C++ 等则不会引入。
# s.frameworks = 'UIKit', 'MapKit' # 所需的 framework,多个用逗号隔开
# s.dependency 'AFNetworking', '~> 2.3' # 依赖关系,该项目所依赖的其他库,如果有多个需要填写多个
# s.dependency 'SDWebImage'
end
编辑完 HHPodTestLibrary.podspec 文件后,需要验证一下这个文件是否可用,如果有任何 WARNING 或者 ERROR 都是不可以的,它就不能被添加到 Spec Repo 中,不过 Xcode 的 WARNING 是可以存在的(即,如果你只是本地使用,允许存在个别 error 和 warning),验证需要执行以下命令:
$ pod lib lint
当你看到下面的命令行输出,就说明验证通过了,不过这只能说明这个 podspec 文件是合格的,并不保证这个 Pod 是可以用的,后边还需要在本地做一下验证。
-> HHPodTestLibrary (0.1.0)
HHPodTestLibrary passed validation.
提交修改(包括 HHPodTestLibrary.podSpec 文件)
$ git add .
$ git commit -m '编辑 podspec 文件'
$ git push origin master
从此处跳转执行 4 的操作,完成 4 的验证操作后(可能需要多次修改 pod 中的文件,并提交)。
三、创建 xxx.podspec 文件
如果执行了 "步骤二" 的操作,请移步 "步骤四" O(∩_∩)O~
如果已经有了现成的项目,那么就需要给这个项目创建一个 podspec 文件,创建它需要执行 CocoaPods 的另外一个命令 pod spec create
,官方文档见 Specs and the Specs Repo。
pod spec create <spec file name> [对应私有仓库的远程地址,需要事先创建好,否则会失败]
$ pod spec create HHPodTestLibrary https://gitee.com/riversea2015/HHPodTestLibrary.git
执行完之后,就创建了一个 podspec 文件,他其中会包含很多内容,可以按照我之前介绍的进行编辑,没用的删掉。编辑完成之后使用验证命令验证一下
$ pod lib lint
验证无误就可以进入下一步了。
* 提交代码,并 push 到 remote repository
* 跳转执行 "步骤四" 的操作,完成 "步骤四" 的验证操作后(有可能需要回来修改 pod 中的文件,并提交),再执行打 Tag 并 push tag 的操作。
* 至此,可以跳转至 "步骤五" 执行后边的操作了。
四、本地调试 xxx.podspec 文件及 Pod 功能
方案一:可以创建一个新的 Demo,在这个项目的 Podfile 文件中直接指定刚才创建编辑好的 podspec 文件,看是否可用。 在 Podfile 中我们可以这样编辑:
platform :ios, '7.0'
# 以下2种方式均可:
pod 'HHPodTestLibrary', :path => '<指定本地 HHPodTestLibrary 目录的路径>'
pod 'HHPodTestLibrary', :podspec => '<指定本地 .podspec 文件路径,含文件名>'
方案二:如果前边是使用 "步骤二" 创建的项目,就会自动生成 Example 项目,只需要 cd 到 Example 目录下,执行:
pod install
# 或者
pod update --no-repo-update
在 Example 中编写代码,测试库功能无误后就可以开始 5 了,提交 podspec 到 Spec Repo 中。
* 如果在 Example 的 Podfile 中写了 use_frameworks! 则可能导致无法引用 pod 中的文件,而且 pod 之间也就互不可见
验证通过后,为了在使用此 pod 时能像常见的第三方库那样指定版本号,需要打 tag,并将 tag 推送到 remote repository:
$ git tag -m 'first release' '0.1.0'
$ git push --tags
五、向 Spec Repo 提交 xxx.podspec
向 Spec Repo
提交 podspec
需要保证 podspec
已通过验证。
向私有 Spec Repo
提交 podspec
只需要一个命令:
# pod repo push <本地 repo 的名字> <.podspec 名字>
$ pod repo push riversea2015_mobile_iphone_repo_local HHPodTestLibrary.podspec #前面是本地 Repo 名字 后面是 .podspec 名字
完成之后这个组件库就添加到我们的私有 Spec Repo 中了,可以进入到 ~/.cocoapods/repos/riversea2015_mobile_iphone_repo_local 目录下查看
.
├── LICENSE
├── HHPodTestLibrary
│ └── 0.1.0
│ └── HHPodTestLibrary.podspec
└── README.md
再去看我们的 Spec Repo 远端仓库,也有了一次提交,这个 podspec 也已经被 Push 上去了。
至此,组件库已经制作添加完成了,使用 pod search
命令就可以搜到这个库了。
$ pod search HHPodTestLibrary
-> HHPodTestLibrary (0.1.0)
Just Testing.
pod 'HHPodTestLibrary', '~> 0.1.0'
- Homepage: https://gitee.com/riversea2015/HHPodTestLibrary
- Source: https://gitee.com/riversea2015/HHPodTestLibrary.git
- Versions: 0.1.0 [riversea2015_mobile_iphone_repo repo]
这里说的是添加到私有 Repo,如果要添加到 CocoaPods 的官方库了,可以使用 trunk 工具,具体可以查看 官方文档。
六、使用制作好的 Pod
完成这一系列步骤之后,就可以像使用其他三方库一样使用自建的 Pod 了,即在项目的 Podfile 里增加以下代码,然后执行 pod install
:
pod 'HHPodTestLibrary', '~> 0.1.0'
如果后期需要更新版本,执行以下命令即可:
pod update HHPodTestLibrary --no-repo-update
打开项目可以看到,我们自己的库文件已经出现在 Pods 子项目中的 Pods 子目录下了,而不再是 Development Pods。
注意:这里 Podfile 文件前两行应该是:
source 'https://github.com/CocoaPods/Specs.git' # GitHub 的公有仓库
source 'https://gitee.com/riversea2015/riversea2015_mobile_iphone_repo.git' # 码云上的私有仓库
七、更新维护 xxx.podspec
最后再来说一下制作好的 HHPodTestLibrary.podspec 文件后续的更新维护工作,比如如何添加新的版本,如何删除 Pod。
现在已经制作好了 HHPodTestLibrary 的 0.1.0 版本,现在对他做一些更新维护工作:
7.1 添加更多的子 pod
这次我添加了更多的模块到 PodTestLibrary 之中,包括工具类,底层 Model 及 UIKit 扩展等,这里又尝试了一下 subspec 功能,给 PodTestLibrary 创建了多个子分支。
具体做法是先将源文件添加到 Pod/Classes 中,然后按照不同的模块对文件目录进行整理,因为我有四个模块,所以在 Pod/Classes 下有创建了四个子目录,完成之后继续编辑之前的 PodTestLibrary.podspec,这次增加了 subspec 特性
#
# Be sure to run `pod lib lint HHPodTestLibrary.podspec' to ensure this is a
# valid spec before submitting.
#
# Any lines starting with a # are optional, but their use is encouraged
# To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html
#
Pod::Spec.new do |s|
s.name = 'HHPodTestLibrary'
s.version = '0.1.3'
s.summary = 'This is the first pod repo test library.'
s.description = <<-DESC
This is the first pod repo test library!
DESC
s.homepage = 'https://gitee.com/riversea2015/HHPodTestLibrary'
# s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'hehai' => 'hehai682@126.com' }
s.source = { :git => 'https://gitee.com/riversea2015/HHPodTestLibrary.git', :tag => s.version.to_s }
# s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'
s.ios.deployment_target = '8.0'
s.requires_arc = true
# s.source_files = 'HHPodTestLibrary/Classes/**/*.{h.m}'
# s.public_header_files = 'HHPodTestLibrary/Classes/**/*.h'
# s.resource_bundles = {
# 'HHPodTestLibrary' => ['HHPodTestLibrary/Assets/*.png']
# }
s.subspec 'First' do |first|
first.source_files = 'HHPodTestLibrary/Classes/First/**/*.{h,m}'
first.public_header_files = 'HHPodTestLibrary/Classes/First/**/*.h'
first.dependency 'AFNetworking', '~> 3.1'
end
s.subspec 'Second' do |second|
second.source_files = 'HHPodTestLibrary/Classes/Second/**/*.{h,m}'
second.public_header_files = 'HHPodTestLibrary/Classes/Second/**/*.h'
second.dependency 'HHPodTestLibrary/First'
end
s.subspec 'Third' do |third|
third.source_files = 'HHPodTestLibrary/Classes/Third/**/*.{h,m}'
third.public_header_files = 'HHPodTestLibrary/Classes/Third/**/*.h'
third.dependency 'FMDB', '~>2.7.1'
end
# s.frameworks = 'UIKit', 'MapKit'
# s.dependency 'AFNetworking', '~> 2.3'
# s.dependency 'OpenUDID', '~> 1.0.0'
end
注意:
1.subspec 'First' do |first| 中 子 pod 的别名必须以小写字母开头,否则 pod lib lint 时会报错,而且并不指明错误原因,会很崩溃的(⊙o⊙)
2.默认 pod 与 pod 之间是可以互相引用的,但是 pod 内部是无法直接引用外部文件的,所以需要我们来做这些事情。
因为我们创建了 subspec,所以项目整体的依赖 dependency、源文件 source_files、头文件 public_header_files、资源文件 resource 等都移动到了各自的 subspec 中,各个 subspec 之间也可以相互依赖,比如 Second 就依赖于 First。
编辑完成之后,在测试项目里执行 pod update
,几个子 pod 都被加进工程了,写代码验证无误之后,就可以将这个工程 push 到远端仓库,并打上新的 tag -> 0.1.3。
最后再次使用 pod lib lint
验证编辑好的 podsepc 文件,没有自身的 WARNING 或者 ERROR 之后,就可以再次提交到 Spec Repo 中了。
$ pod repo push riversea2015_mobile_iphone_repo_local HHPodTestLibrary.podspec
之后再次到 ~/.cocoapods/repos/riversea2015_mobile_iphone_repo_local 目录下查看
.
├── HHPodTestLibrary
│ ├── 0.1.0
│ │ └── HHPodTestLibrary.podspec
│ ├── 0.1.2
│ │ └── HHPodTestLibrary.podspec
│ └── 0.1.3
│ └── HHPodTestLibrary.podspec
├── LICENSE
└── README.md
4 directories, 5 files
已经有 3 个版本了,使用 pod search 查找得到的结果为
-> HHPodTestLibrary (0.1.3)
This is the first pod repo test library.
pod 'HHPodTestLibrary', '~> 0.1.3'
- Homepage: https://gitee.com/riversea2015/HHPodTestLibrary
- Source: https://gitee.com/riversea2015/HHPodTestLibrary.git
- Versions: 0.1.3, 0.1.2, 0.1.0 [riversea2015_mobile_iphone_repo_local repo]
- Subspecs:
- HHPodTestLibrary/First (0.1.3)
- HHPodTestLibrary/Second (0.1.3)
- HHPodTestLibrary/Third (0.1.3)
(END)
完成这些之后,在实际项目中我们就可以选择使用整个组件库或者是组件库的某一子库了,对应的 Podfile 中添加的内容为
platform :ios, '8.0'
pod 'HHPodTestLibrary/Second', '~>0.1.0' # 使用某一个部分(导入了2个子库)
# pod 'HHPodTestLibrary/Third', '~> 0.1.0' # 使用某一个部分(导入了1个子库)
# pod 'HHPodTestLibrary', '~> 0.1.0' # 使用整个库
注意:此处,因为 Second 依赖于 First,所以当 pod update 之后,实际导入项目中的子库总共有2个:First、Second。
7.2 删除、恢复私有 Spec Repo
最后介绍一下如何删除一个私有 Spec Repo,只需要执行一条命令即可
$ pod repo remove riversea2015_mobile_iphone_repo_local
这样,这个 Spec Repo 就在本地删除了,我们还可以通过下边这行命令,再把它给加回来。
$ pod repo add riversea2015_mobile_iphone_repo_local https://gitee.com/riversea2015/riversea2015_mobile_iphone_repo.git
7.3 删除私有 Spec Repo 下的某一个 podspec
如果要删除私有 Spec Repo 下的某一个 podspec,无需借助 CocoaPods,只需要切到 riversea2015_mobile_iphone_repo_local 目录下,删掉库目录:
$ cd ~/.cocoapods/repos/riversea2015_mobile_iphone_repo_local
$ rm -Rf HHPodTestLibrary
然后在将 Git 的变动 push 到远端仓库即可。
$ cd ~/.cocoapods/repos/riversea2015_mobile_iphone_repo_local
$ git add --all .
$ git ci -m "remove unuseful pods"
$ git push origin master
八、错误处理
常见的错误处理可以参考 这篇文章 的 “报错问题” 部分,个人觉得这位前辈写的已经比较全面了。 2022.10.07 突然发现那篇文章的链接失效了,以后还是自己记录吧-_-||
另外,如果是在码云上边创建的 私有仓库
,那么执行 pod lib lint
的时候,可能会报以下错误:
The URL (https://gitee.com/xxx/xxx) is not reachable.
最后在 stackoverflow 上找到了参考的解决方案 :https://stackoverflow.com/questions/27303475
原因是 pod spec linter 仅检查 master specs,所以找不到私有库,可以通过增加 --sources选项解决,即:
pod spec lint --sources='git@our-private-spec-repo:iOS/Specs.git,https://github.com/CocoaPods/Specs'
# 存在的问题
使用文中方式创建的 pod 库,如果要添加文件,必须打开本地目录创建或拖拽文件,操作不便,另外还可以通过创建 framwork 的方式创建私有 pod,会在下一篇中记录。
虽然在 pod 开发过程中有分目录,但是在项目中导入该 pod 后,并不会出现分目录的样式,都放在了一个文件夹里(除子pod外),这个问题暂时还没找到很好的解决方案。