Tenloy's Blog

私有Pod库部署

Word count: 2.9kReading time: 11 min
2021/05/03 Share

目的:构建自己的一个组件,放在公司的私有pod库中,然后可以通过cocoapod来导入这个pod库。

一、几个概念

在构建私有库之前,先了解以下几个基本概念

  • repo(repository仓库)
  • Pod(代码库)。分为:(Pod译为(宇宙飞船的)分离舱,Pod对于主项目,如iPod之于Mac,AirPods之于iPhone)
    • 公有库:放在外网上,其他开发者是可以访问使用的
    • 私有库:放在本地或者内网上,只有公司内部的人员可以使用
  • podspec文件(spec说明书)。创建pod库的时候会用到podspec文件将lib的一些信息,比如:版本号、作者名,链接地址等,以及其中用到的包括资源、源码、需要引用的framework,以及第三库等等组织起来。
  • Spec Repo(存放spec的仓库)。顾名思义,就是存放Spec文件的仓库,就是一个容器,所有公开的Pods都在这个里面,是一个Git仓库remote端。
    • 执行 pod setup命令会clone该仓库到本地的~/.cocoapods/repos目录下,可以进入到这个目录看到master文件夹就是这个官方的Spec Repo了。
    • Spec Repo仓库保存了依赖库的名称,版本号,以及spec文件。
    • 因为Spec Repo存放了所有的spec文件,所以很明显它是一种集中式的依赖库管理工具

创建公有Pod库或者私有Pod库,实际上原理是一样的,都是基于git服务和repo协议。

不一样的是,两者的版本索引查询方式不一样,公有库的podspec由CocoaPods/Specs管理,而内部私有使用的pod库需要自己建立一个仓库(Spec Repo)来管理podspec。

好了,下面我们正式来构建私有的pod库

二、构建私有pod库

2.1 创建组件仓库

在公司的私有Git服务器上创建git仓库,用来管理我们的组件模块,将仓库Clone下来,把模块代码Push上去。

2.2 创建组件的.podspec文件

私有仓库的根目录下,执行下面命令,创建一个新的.podspec文件,在这个文件中来进行私有仓库的一些配置工作。

1
pod spec create TestModule # 生成TestModule.podspec

2.2.1 编写.podspec(源码或framework)

podspec 文件里面的东西,描述了我们代码库的一些特征,例如namesourceversion之类的,在进行pod search操作的时候也会显示。

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
Pod::Spec.new do |s|
s.name = "TestModule"
s.version = "0.0.5" // 当前版本号,假如你后续更新了新版本,需要修改此处。与组件中的tag保持一致。
s.summary = "Test" // A short description of TestModule.
s.description = <<-DESC
"用于生成tags和改进搜索结果,尽量剪短、切中要点"
DESC
s.homepage = "http://gitlab.100credit.cn/xxx/TestModule" // 页面链接。可以是个介绍文档
s.license = "MIT"
s.author = { "username" => "yyy@163.com" }
s.source = { :git => "http://gitlab.100credit.cn/xxx/TestModule.git", :tag => "#{s.version}" } // 代码仓库的地址

// **/* 的写法表示Classes所有路径下的所有匹配文件
s.exclude_files = "Classes/Exclude" // 不需要导入的文件路径
s.source_files = "Classes", "Classes/**/*.{h,m}" // 需要导入到项目中的文件
s.public_header_files = 'Classes/*.h', 'Classes/**/*.h' // 配置公有的头文件(.h文件)

// 资源文件如.bundle,.png,.txt等,这些会被放到mainBundle中,要注意避免发生命名重复的问题。
s.resources = 'Class/**/*.{png}'
// 资源文件(配置的文件会放到你自己指定的bundle中,可以解决resources导致的命名冲突问题)
s.resource_bundles = { 'ResourceBundleA' => ['Class/*.jpg'] }

// 依赖的系统framework。将您的库与框架或库链接起来。库不包括其名称的lib前缀。
s.frameworks = 'UIKit'
// s.vendored_frameworks = '' // 依赖的非系统framework。
// s.libraries = '' // 依赖的系统库。要忽略lib前缀
// s.vendored_libraries = '' // 依赖的非系统库。要带lib前缀
// s.dependency "JSONKit", "~> 1.4" // 依赖的其他的pod库,或自身的subspec (depend on other Podspecs)

// 如果你的library依赖于编译器标记 compiler flags,可以在xcconfig hash中设置它们
// s.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" }

s.platform = :ios
end

以上 source 字段表示导入的是源码形式的依赖。也可以是 framework 形式。两种方式,各有利弊。

  • 直接在主工程中集成代码文件,可以看到其内部实现源码,方便在主工程中进行调试
  • 集成framework的方式,可以加快编译速度,而且对每个组件的代码有很好的保密性。如果公司对代码安全比较看重,可以考虑framework的形式。

以友盟的SDK为例,UMCCommon.podspec.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"name": "UMCCommon",
"version": "7.3.5",
"summary": "UMeng+ component SDK",
"description": "友盟+组件化SDK基础库UMCommon",
"homepage": "https://developer.umeng.com/docs/66632/detail/66885", # 文档链接
"license": {
"type": "Copyright",
"text": "Copyright 2011 - 2021 umeng.com. All rights reserved.\n"
},
"authors": { "UMeng": "support@umeng.com" },
"source": { "http": "https://umplus-sdk-download.oss-cn-shanghai.aliyuncs.com/iOS/UMCommon/UMCommon_7.3.5.zip" },
# 解压后的目录如下:
# UMCommon_7.3.5
# ┗━━━━ UMCommon.framework
"platforms": { "ios": "6.0" },
"requires_arc": true,
"ios": { "vendored_frameworks": "UMCommon_7.3.5/*.framework" }, # 将上面source解压出的framework导入
"libraries": [ "sqlite3", "z"], # 系统静态库
"frameworks": [ "CoreTelephony", "SystemConfiguration"], # 系统framework
"dependencies": { "UMAPM": [], "UMDevice": [] }
}

2.2.2 使用subspec目录分层

目录分层的好处:

  • 目录分层,结构清晰;
  • 使用pod引入一个三方库时,可以只引入一个subspec而不用将整个三方库引入。

例如AFNetworking:

下面举例说明,如图所示的目录结构:

1
2
3
4
5
6
7
8
9
ZCPKit
┗━━━━Classes
┗━━━━ZCPKit.h
┗━━━━ZCPRouter
┗━━━━ZCPRouter.h
┗━━━━ZCPRouter.m
┗━━━━ZCPUtil
┗━━━━ZCPUtil.h
┗━━━━ZCPUtil.m

写法如下:

效果:

如果想有多层的目录结构还可以继续嵌套下去。

有几个需要注意的地方:

  • 层级不能出现循环依赖。
    • 比如类ZCPUtil.h中 #import “ZCPRouter.h”,同时ZCPRouter.h中 #import “ZCPUtil.h”。这样当写podspec时就需要在Util层级中写dependency ‘ZCPKit/Router’,在Router层级中写dependency ‘ZCPKit/Util’。如此便存在Router与Util层级之间的循环依赖。出现循环依赖时,三方库是无法成功提交到repo上的,会报依赖错误。解决办法是,层级间要尽量解耦。
  • 分层的层级不要太多,层级不要太深。为了避免出现上述的循环依赖错误。
  • source_files使用的是真实的物理路径,而dependency依赖其他层级时使用的是层级路径,不是真实的物理路径。
    • 例如:ZCPUtil.h文件的真实路径是:ZCPKit/Framework/Util,而Util层级是属于ZCPKit层级下的一个子subspec,所以当写Router层级依赖Util层级时要写:dependency ‘ZCPKit/Util’而不是dependency ‘ZCPKit/Framework/Util’

2.2.3 校验

改完,可以用pod命令验证一下这个.podspec文件有没有问题。

1
2
$ pod spec lint # pod spec: Manage pod specs.  + lint: Validates a spec file
# 如果没问题会提示 passed validation

2.3 创建Spec Repo

在公司的私有Git服务器上创建git仓库,用来做内部私有库的Spec Repo,盛放所有功能组件的spec。

这个Spec Repo必须添加到本地,否则组件clone不下来

1
2
3
4
5
6
#  执行这个命令的人,必须得有这个库的操作权限
$ pod repo add 库的名字 库的地址

# 比如:
$ pod repo add TestSpecRepo git@git.xxxxx/TestSpecRepo.git # 将ymtSpecs添加到本地repo
# 添加成功后可以在~/.cocoapods/repos/目录下可以看到官方的specs:master和刚刚加入的specs:TestSpecRepo

添加完成后,用下面命令检查一下:

1
pod repo lint # pod repo: Manage spec-repositories.  lint: Validates all specs in a repo

2.4 将podspec同步到Spec Repo

之后,当我们修改了Pod库源代码后,我们也要更新.podspec文件,并同步到spec 仓库。

  1. 修改podspec中的version
  2. 提交代码,打Tag,注意:这个tag记得要提交到origin,默认是只在工作副本中打Tag,”tag = podspec中的version
  3. pod repo push <Specs库名> <Pod库名>.podspec 将podspec推送到 spec Repo中

2.5 使用私有Pod库代码

Spec私有仓库中有可用的代码后,就可以通过CocoaPods命令来使用组件代码了。在Podfile文件中需要声明私有sepc repo地址,例如下面代码。

1
2
3
4
5
6
7
8
9
10
11
source 'https://github.com/CocoaPods/Specs.git'  # CocoaPods官方的sepc repo
source 'git@git.xxxxx/TestSpecRepo.git' # spec repo仓库的git地址

target 'MainProject' do
# 第三方库
pod 'Masonry'
pod 'MGJRouter'

# 私有仓库
pod 'TestModule', '~> 1.1'
end

版本控制也非常简单,只需要在Podfile中指定某个私有仓库的版本号即可。

三、遇到的问题

The NAME.podspec specification does not validate

用pod spec lint –verbose 验证一下,会打印出很多的详细信息,此时打印:The spec did not pass validation, due to 1 warning (but you can use --allow-warnings to ignore it).

此时,可重新使用pod repo push Specs xx.podspec –allow-warnings。

更新某个库时,连接超时。fatal: unable to access '...url...': Failed to connect to ...host... port 443: Operation timed out。解决步骤:

  1. pod repo 列出所有(公有库、私有库的)本地库,查看 cocoapods的本地库路径,一般都是.cocoapods/repos/master
  2. 从报错信息中,可以看到操作失败的三方库的名称、版本号,使用shell命令 find进行查找
    find ~/.cocoapods/repos/master -iname 名称,会返回在本地库中的路径。前往该路径
  3. 三方库文件夹下会有多个版本文件夹,找到报错信息中的版本号对应的文件夹,里面会有一个xxx.podspec.json,替换json中source下git地址(可以直接Google,也可以在GitHub上搜索该库名称,看项目介绍,是否是这个库的镜像)。

四、Local Pods

私有仓库调试比较麻烦,不可能是每修改一些代码,就提交到远端仓库,再拉下来验证代码是否有效。所以,为了提升开发效率,可以通过LocalPods的方式进行调试。或者不想用远端仓库,也可以直接用LocalPods的方案进行本地组件化,但是并不推荐这种方案,因为不能进行物理隔离。

本地LocalPods仓库不需要单独创建,直接用之前的组件仓库。在pods中使用本地仓库时,需要指定对应路径。指定路径后,执行pod install即可集成本地仓库。为了方便指定本地路径,建议将组件仓库尽量放在一个文件夹下,例如叫做LocalPods

1
pod 'Login', :path => './LocalPods/Login/'

通过上面的命令集成本地仓库后,Pods会生成一个和远程仓库文件夹同级的文件,叫做Development Pods,这个文件夹就是本地仓库所在的位置。

此时之前Login仓库的产物就被替换为本地 LocalPods/Login 代码,后面的修改都会影响这个产物。直到调试完成,将代码提交到组件仓库,将path路径删除重新执行pod install,产物就会被替换为我们的私有Pod库代码

Author:Tenloy

原文链接:https://tenloy.github.io/2021/05/03/iOS-private-pod.html

发表日期:2021.05.03 , 10:09 AM

更新日期:2024.04.07 , 8:02 PM

版权声明:本文采用Crative Commons 4.0 许可协议进行许可

CATALOG
  1. 一、几个概念
  2. 二、构建私有pod库
    1. 2.1 创建组件仓库
    2. 2.2 创建组件的.podspec文件
      1. 2.2.1 编写.podspec(源码或framework)
      2. 2.2.2 使用subspec目录分层
      3. 2.2.3 校验
    3. 2.3 创建Spec Repo
    4. 2.4 将podspec同步到Spec Repo
    5. 2.5 使用私有Pod库代码
  3. 三、遇到的问题
  4. 四、Local Pods