使用 CocoaPods 创建并管理私有仓库

CocoaPods管理私有库.png

0.前言

CocoaPods 是一个比较常用的 iOS 依赖管理工具,我们目前的项目中就在使用,今天抽空做一个小结,记录一下在 Git 环境下如何使用 CocoaPods 创建并管理我们私有仓库。

创建私有仓库的主要步骤:

  • 创建私有的 Spec Repo。(从未创建过私有库时需要,只需创建一次)
  • 创建(具体的 Pod 需要的)项目工程文件。(2 和 3 二选一)
  • 创建(具体的 Pod 对应的) xxx.podspec 文件。(2 和 3 二选一)
  • 本地测试 xxx.podspec 文件及 Pod 库 功能。
  • 向私有的 Spec Repo 中提交 xxx.podspec。
  • 使用创建好的 Pod 库。
  • 更新维护 xxx.podspec。

1.创建私有 Spec Repo

首先介绍一下 Spec Repo,它是所有的 Pods 的一个索引,实际也是一个 Git 仓库,远端在 GitHub、其他代码托管平台或自家的服务器上,当使用了 CocoaPods 后会被clone到本地的 ~/.cocoapods/repos 目录下,进入此目录后看到的 master 文件夹就是官方的 Spec Repo,本文中自己创建的私有库会在与 master 平级的位置。master 目录的结构大概是这样的(缩减了几个层级):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.
├── 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 中执行如下命令:

1
2
# 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

说明 1:riversea2015_mobile_iphone_repo_local 是本地显示的名字,可以和后边链接里的 riversea2015_mobile_iphone_repo 名称不同(⊙o⊙)哦!

说明 2:如果有其他合作人员共同使用这个私有 Spec Repo 的话,在他有对应 Git 仓库的权限的前提下,执行相同的命令添加这个 Spec Repo 即可。

至此第一步创建私有 Spec Repo 完成。

说明 3:如果有现有的组件项目,并且在 Git 的版本管理下,那么可以直接进行第 3 步了;如果你的组件还在你冗余庞大的项目中,需要拆分出来或者需要自己从零开始创建一个组件库,建议使用 CocoaPods 提供的一个工具创建工程文件,详见第 2 步。

2.创建 Pod 项目工程文件

2.1 创建项目( pod + Example)

此处使用 CocoaPods 提供的一个工具创建单独的 pod 项目文件,工具的文档介绍是 Using Pod Lib Create, 就拿我创建的 HHPodTestLibrary 为例介绍一下具体操作:

1
2
3
4
5
# 先cd到要创建项目的目录然后执行
$ cd /Users/sea/Documents

# 然后开始创建
$ pod lib create HHPodTestLibrary

之后他会问你5个问题 (具体介绍见官方文档,箭头后边是我的选择):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

Would you like to do view based testing? [ Yes / No ] // 是否要做 UI 测试
> yes

What is your class prefix? // 定义类前缀
> HH

回答完以上5个问题,会自动执行 pod install 命令创建项目并生成依赖,然后会自动使用 Xcode 打开工程。

2.2 添加库文件,并提交远端仓库

首先在 Pod 的 Classes 中添加了 4 个文件,然后进入 Example 文件夹执行 pod update 命令,再打开项目工程可以看到,刚刚添加的组件已经在 Pods 子工程下的 Development Pods/HHPodTestLibrary 中了。

测试无误后需要将该项目添加并推送到远端仓库。

通过 CocoaPods 创建出来的目录本身就在本地的 Git 管理下,我们需要做的就是给它添加远端仓库,同样去 GitHub 或其他的 Git 服务提供商那里创建一个私有的仓库,拿到 SSH 或 HTTPS 地址,然后 cd 到 PodTestLibrary 目录,提交修改:

1
2
3
4
$ 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 # 提交到远端仓库

2.3 补充说明:

1.如果一开始在远端创建的仓库里有文件,执行 git push origin master 的时候是推不上去的,应该会提示你执行 git pull origin master 命令拉取并合并远端代码,不过,当你执行此命令时,又会报下边的错:

1
fatal: refusing to merge unrelated histories

此时,可以执行 git pull origin master --allow-unrelated-histories 命令强制合并 2 个不相干的 git 仓库,然后使用正常的 git push origin master 就可以了推上去了。

其实,还有一种更加简便也更加合理的方式解决这个问题,那就是在远端创建仓库时一个文件都不要加,如下图:

本地仓库与远端仓库的关联方式.png

然后在终端已经存在的 git 仓库中执行以下操作(上图虚线框中的命令)就可以推上去了:

1
2
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.4 配置/修改 xxx.podspec 文件

执行完上边的操作之后,就该编辑 HHPodTestLibrary.podspec 文件了,这是一个 Ruby 的文件,把编辑器的格式改成 Ruby,就能看到语法高亮,下面我贴上我的 podspec 文件,并在后面以注释的形式说明每个字段的含义,没有涉及到的字段可以去官方文档查阅。(此处我的文件没有删除注释,实际操作时可以删掉注释)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#
# 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),验证需要执行以下命令:

1
$ pod lib lint

当你看到

1
2
-> HHPodTestLibrary (0.1.0)
HHPodTestLibrary passed validation.

就说明验证通过了,不过这只是这个 podspec 文件是合格的,不一定说明这个 Pod 是可以用的,后边还需要在本地做一下验证。

提交修改(包括 HHPodTestLibrary.podSpec 文件)

1
2
3
$ git add .
$ git commit -m '编辑 podspec 文件'
$ git push origin master
* 从此处跳转执行 4 的操作,完成 4 的验证操作后(有可能需要回来修改 pod 中的文件,并提交),再接着执行下边的打 Tag 操作。

因为 HHPodTestLibrary.podspec 文件中获取 Git 版本控制的项目还需要 tag 号,所以我们要打上一个 tag,并将 tag 推送到 remote repository:

1
2
$ git tag -m 'first release' '0.1.0'
$ git push --tags
* 至此,可以跳转至 5 执行后边的操作了。

3.创建 具体的Pod 对应的 xxx.podspec 文件。(“步骤二” 和 “步骤三” 2选1)

如果执行了 “步骤二” 的操作,请绕行至 “步骤四” O(∩_∩)O~

如果已经有了现成的项目,那么就需要给这个项目创建一个 podspec 文件,创建它需要执行 CocoaPods 的另外一个命令,官方文档在这里

1
2
pod spec create <spec file name> [对应私有仓库的地址,需要实现创建好,否则会失败]
$ pod spec create HHPodTestLibrary https://gitee.com/riversea2015/HHPodTestLibrary.git

执行完之后,就创建了一个 podspec 文件,他其中会包含很多内容,可以按照我之前介绍的进行编辑,没用的删掉。编辑完成之后使用验证命令验证一下

1
$ pod lib lint

验证无误就可以进入下一步了。

* 提交代码,并 push 到 remote repository
* 跳转执行 "步骤四" 的操作,完成 "步骤四" 的验证操作后(有可能需要回来修改 pod 中的文件,并提交),再执行打 Tag 并 push tag 的操作。
* 至此,可以跳转至 "步骤五" 执行后边的操作了。

4.本地测试 xxx.podspec 文件及 Pod 功能

方案一:可以创建一个新的 Demo,在这个项目的 Podfile 文件中直接指定刚才创建编辑好的 podspec 文件,看是否可用。 在 Podfile 中我们可以这样编辑:

1
2
3
4
platform :ios, '7.0'
# 以下2种方式均可:
pod 'HHPodTestLibrary', :path => '<指定本地 HHPodTestLibrary 目录的路径>'
pod 'HHPodTestLibrary', :podspec => '<指定本地 .podspec 文件路径,含文件名>'

方案二:如果前边是使用 “步骤二” 创建的项目,就会自动生成 Example 项目,只需要 cd 到 Example 目录下,执行:

1
2
3
pod install
# 或者
pod update --no-repo-update

在 Example 中编写代码,测试库功能无误后就可以开始 5 了,提交 podspec 到 Spec Repo 中。

* 如果在 Example 的 Podfile 中写了 use_frameworks! 则可能导致无法引用 pod 中的文件,而且 pod 之间也就互不可见

5.向 Spec Repo 提交 xxx.podspec

向 Spec Repo 提交 podspec 需要完成两点:

①是 podspec 必须通过验证无误;

②就是删掉无用的注释(这个不是必须的,为了规范还是删掉吧)。

向我们的私有 Spec Repo 提交 podspec 只需要一个命令:

1
2
# 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 目录下查看

1
2
3
4
5
6
.
├── LICENSE
├── HHPodTestLibrary
│ └── 0.1.0
│ └── HHPodTestLibrary.podspec
└── README.md

再去看我们的 Spec Repo 远端仓库,也有了一次提交,这个 podspec 也已经被 Push 上去了。

至此,我们的这个组件库就已经制作添加完成了,使用 pod search 命令就可以查到我们自己的库了

1
2
3
4
5
6
7
$ 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 工具,具体可以查看 官方文档

6.使用制作好的 Pod

在完成这一系列步骤之后,我们就可以在正式项目中使用这个私有的 Pod 了只需要在项目的 Podfile 里增加下边这行代码即可

1
$ pod 'HHPodTestLibrary', '~> 0.1.0'

然后执行执行以下命令:

1
pod update --no-repo-update # 更新库依赖

打开项目可以看到,我们自己的库文件已经出现在 Pods 子项目中的 Pods 子目录下了,而不再是 Development Pods。

注意:这里 Podfile 文件前两行应该是:

1
2
source 'https://github.com/CocoaPods/Specs.git'	# GitHub 的公有仓库
source 'https://gitee.com/riversea2015/riversea2015_mobile_iphone_repo.git' # 码云上的私有仓库

7.更新维护 xxx.podspec

最后再来说一下制作好的 HHPodTestLibrary.podspec 文件后续的更新维护工作,比如如何添加新的版本,如何删除 Pod。

现在已经制作好了 HHPodTestLibrary 的 0.1.0 版本,现在对他做一些更新维护工作:

7.1 添加更多的子 pod

这次我添加了更多的模块到 PodTestLibrary 之中,包括工具类,底层 Model 及 UIKit 扩展等,这里又尝试了一下 subspec 功能,给 PodTestLibrary 创建了多个子分支。

具体做法是先将源文件添加到 Pod/Classes 中,然后按照不同的模块对文件目录进行整理,因为我有四个模块,所以在 Pod/Classes 下有创建了四个子目录,完成之后继续编辑之前的 PodTestLibrary.podspec,这次增加了 subspec 特性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#
# 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

注意:

* .subspec 'First' do  |first| 中 子 pod 的别名必须以小写字母开头,否则 pod lib lint 时会报错,而且并不指明错误原因,会很崩溃的(⊙o⊙)哦
* 默认 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 中了。

1
$ pod repo push riversea2015_mobile_iphone_repo_local HHPodTestLibrary.podspec

之后再次到 ~/.cocoapods/repos/riversea2015_mobile_iphone_repo_local 目录下查看

1
2
3
4
5
6
7
8
9
10
11
12
.
├── 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 查找得到的结果为

1
2
3
4
5
6
7
8
9
10
11
-> 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 中添加的内容为

1
2
3
4
5
6
7
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,只需要执行一条命令即可

1
$ pod repo remove riversea2015_mobile_iphone_repo_local

这样,这个 Spec Repo 就在本地删除了,我们还可以通过下边这行命令,再把它给加回来。

1
$ 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 目录下,删掉库目录:

1
2
$ cd ~/.cocoapods/repos/riversea2015_mobile_iphone_repo_local
$ rm -Rf HHPodTestLibrary

然后在将 Git 的变动 push 到远端仓库即可。

1
2
3
4
$ cd ~/.cocoapods/repos/riversea2015_mobile_iphone_repo_local
$ git add --all .
$ git ci -m "remove unuseful pods"
$ git push origin master

8.错误处理

常见的错误处理可以参考 这篇文章 的 “报错问题” 部分,个人觉得这位前辈写的已经比较全面了。

另外,如果是在码云上边创建的 私有仓库,那么执行 pod lib lint 的时候,可能会报以下错误:

1
The URL (https://gitee.com/xxx/xxx) is not reachable.

最后在 stackoverflow 上找到了参考的解决方案 :https://stackoverflow.com/questions/27303475

# 参考资料

0%