基于cocoapods的私有库搭建

最近想为公司搭建cocoapods私有库框架,老早之前做过,踩过不少坑,想不到又一次掉坑里。果真是好记性不如烂笔头,这次得记下来。

1. 前言

cocoapods早已不是新鲜技术,作为iOS平台上的依赖管理工具,方便我们日常开发过程中管理和更新第三方库。基本使用方式不再赘述,这里说一下需求。项目规模大了之后,往往需要将各个业务功能拆分封装,一方面解耦,另一方面可作为第三方库供其他项目组使用。
所以产生一个需求:公司各开发团队,将自己的业务,封装为私有库,上传到公司私有git上,利用cocoapods统一管理。

分解需求,我们需要:
  • 创建私有spec repo(相当于cocoapods私有库资源中心https://github.com/CocoaPods/Specs.git),所有的私有库上传记录在私有spec中
  • 工程封装为私有库,上传到私有spec repo中
  • 项目工程集成私有库

2. 搭建

1. 创建私有spec repo

什么是spec repo

在Podfile中,我们通过

pod 'repoName'

来配置要加载的第三方库。repoName对应的repo地址并没有写出来,因为我们会到https://github.com/CocoaPods/Specs.git中去查找。它相当于一个容器,将所有私有库的podspec文件放到这个spec repo中,建立索引,提供私有库配置信息。
通过pod repo list命令,查看mac 上的repo列表:

HotacooldeMacBook-Pro:0-Venus hotacool$ pod repo list

master
- Type: git (master)
- URL:  https://github.com/CocoaPods/Specs.git
- Path: /Users/hotacool/.cocoapods/repos/master

本地spec repo的大致结构:

.
├── Specs
    └── [SPEC_NAME]
        └── [VERSION]
            └── [SPEC_NAME].podspec

进入/Users/hotacool/.cocoapods/repos/master,可以看到目录结构:


1514169190670.jpg
创建remote&本地clone

理解了上面这个知识点,我们知道了需要创建一个自己的spec repo。

  • 在代码托管平台(github,Bitbucket,码云等),创建spec repo的托管空间
    例如在码云上创建一个spec repo:https://gitee.com/hotacool/HACSpec.git
  • 利用cocoapods提供command,在terminal上运行:
// Clones `URL` in the local spec-repos directory at `~/.cocoapods/repos/`. The
      remote can later be referred to by `NAME`.
pod repo add HACSpec https://gitee.com/hotacool/HACSpec.git

会看到从remote地址git clone spec repo到本地,再次查看本地repo列表,可以看到我们创建的spec repo:

HotacooldeMacBook-Pro:0-Venus hotacool$ pod repo list

HACSpec
- Type: git (master)
- URL:  https://gitee.com/hotacool/HACSpec.git
- Path: /Users/hotacool/.cocoapods/repos/HACSpec

master
- Type: git (master)
- URL:  https://github.com/CocoaPods/Specs.git
- Path: /Users/hotacool/.cocoapods/repos/master

这一步是在本地添加私有库source,其他开发人员Podfile需要集成私有spec上的私有库,都需要首先通过pod repo add命令,本地clone spec repo。
另外一些命令也可能会用到:

// 查看本地spec repo列表
pod repo list
// 更新本地specName的spec repo。私有库podspec文件更改并push到spec repo后,往往需要update
pod repo update specName
// 本地删除spec repo
pod repo remove specName

2. 私有库制作

创建一个基本(不包含代码)的私有库

创建私有库之前,首先需要解耦独立出单独的组件项目。如果已有独立的组件项目,可以通过pod spec create直接创建podspec文件,然后配置,生成私有库。这里首先讲一下,如何完整的创建一个新的私有库,并push到我么自己的spec repo中。
官网上有详细的命令解释文档。下面以创建一个名为HStockCharts的私有库为例:

  • 创建新的lib库
pod lib create HStockCharts

cocoapods会clone pod-template模板来生成HStockCharts,创建过程中会问几个问题:

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 ]
 > None

Would you like to do view based testing? [ Yes / No ]
 > No

What is your class prefix?
 > H

Running pod install on your new library.

一般我会按上面代码来生成一个带example的例子工程,这样生成的example相对干净。也可以选择自动集成testing framework,或者view based testing,这样cocoapods会自动帮你集成一些额外的三方库。
这样生成的目录结构:

HotacooldeMacBook-Pro:HStockCharts hotacool$ tree -L 2
.
├── Example
│   ├── HStockCharts
│   ├── HStockCharts.xcodeproj
│   ├── HStockCharts.xcworkspace
│   ├── Podfile
│   ├── Podfile.lock
│   ├── Pods
│   └── Tests
├── HStockCharts
│   ├── Assets
│   └── Classes
├── HStockCharts.podspec
├── LICENSE
├── README.md
└── _Pods.xcodeproj -> Example/Pods/Pods.xcodeproj

10 directories, 5 files

HStockCharts.podspec是私有库的配置文件;HStockCharts文件夹下是私有库代码文件,Assets是放资源,Classes下是编译的源文件;Example是自动生成的测试工程。

  • 编辑.podspec文件
    .podspec文件是私有库的配置文件。新创建的lib中,自动生成了HStockCharts.podspec,填充了一些默认配置,我们需要手动去编辑。cocoapods是基于Ruby语音写的,所以可以设置为Ruby语法高亮。
Pod::Spec.new do |s|
  s.name             = 'HStockCharts'
  s.version          = '0.1.0'
  s.summary          = 'A short description of HStockCharts.'

# This description is used to generate tags and improve search results.
#   * Think: What does it do? Why did you write it? What is the focus?
#   * Try to keep it short, snappy and to the point.
#   * Write the description between the DESC delimiters below.
#   * Finally, don't worry about the indent, CocoaPods strips it!

  s.description      = <<-DESC
TODO: Add long description of the pod here.
                       DESC

  s.homepage         = 'https://github.com/shisosen@163.com/HStockCharts'
  # s.screenshots     = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
  s.license          = { :type => 'MIT', :file => 'LICENSE' }
  s.author           = { 'shisosen@163.com' => 'shisosen@163.com' }
  s.source           = { :git => 'https://github.com/shisosen@163.com/HStockCharts.git', :tag => s.version.to_s }
  # s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'

  s.ios.deployment_target = '8.0'

  s.source_files = 'HStockCharts/Classes/**/*'
  
  # s.resource_bundles = {
  #   'HStockCharts' => ['HStockCharts/Assets/*.png']
  # }

  # s.public_header_files = 'Pod/Classes/**/*.h'
  # s.frameworks = 'UIKit', 'MapKit'
  # s.dependency 'AFNetworking', '~> 2.3'
end

各标签(s.name等)的具体意义可以参照官方文档。这里先列举几个重要的地方:

s.name //名称,pod search 查找
s.version //版本号,私有库git tag。如果s.source里设置了s.version.to_s,需要注意与私有库git的tag相匹配。
s.source //重要,私有库的remote 地址和tag。也可以:branch => 'master'设置分支。
s.source_files //#代码源文件地址,**/*表示Classes目录及其子目录下所有文件;如果有多个目录下则用逗号分开如['','','']形式
s.resource_bundles //资源文件地址
s.public_header_files //公开的头文件地址
s.frameworks //所需的framework,多个用逗号隔开
s.dependency //依赖关系,该项目所依赖的其他库,如果有多个需要填写多个s.dependency。cocoapods会自动将依赖的其他库集成到工程中。

现在只简单的对source等配置(s.source,s.description)做一下修改:

Pod::Spec.new do |s|
  s.name             = 'HStockCharts'
  s.version          = '0.1.0'
  s.summary          = 'HStockCharts.'

  s.description      = <<-DESC
HStockCharts is a library for stock charts.
                       DESC

  s.homepage         = 'https://gitee.com/hotacool'
  s.license          = { :type => 'MIT', :file => 'LICENSE' }
  s.author           = { 'hotacool' => 'shisosen@163.com' }
  s.source           = { :git => 'https://gitee.com/hotacool/HStockCharts.git', :tag => s.version.to_s }

  s.ios.deployment_target = '8.0'

  s.source_files = 'HStockCharts/Classes/**/*'
  
  # s.resource_bundles = {
  #   'HStockCharts' => ['HStockCharts/Assets/*.png']
  # }

  # s.public_header_files = 'Pod/Classes/**/*.h'
  # s.frameworks = 'UIKit', 'MapKit'
  # s.dependency 'AFNetworking', '~> 2.3'
end
  • push 私有库到remote,validation
    现在在本地已完成私有库的创建,需要将私有库push到远程仓库,这样其他人才能到远程仓库clone。这里将本地HStockCharts push到https://gitee.com/hotacool/HStockCharts.git,即podspec设置的source地址。
    因为我们podspec s.source设置了:tag => s.version.to_s,version是0.1.0,所以需要打tag,注意tag应与version保持一致:
git tag -m "release v.0.1.0 "0.1.0"
git push --tags     //推送tag到远端仓库

然后开始通过pod lib lint 对私有库进行validation:

HotacooldeMacBook-Pro:HStockCharts hotacool$ pod lib lint

 -> HStockCharts (0.1.0)

HStockCharts passed validation.

验证通过。默认情况下,如果验证出现warning,是不能通过验证的。可以通过添加--allow-warnings来允许warnings。

  • push私有库到spec repo
    我们已经在本地搭建好了一个私有库,在Example中,Podfile通过相对地址,引入HStockCharts。
pod 'HStockCharts', :path => '../'

现在需要将私有库推送到我们的spec repo中,从而可以从远程clone。
之前已经在本地通过pod repo add,新加入了我们自己的spec repo: HACSpec。通过命令:

pod repo push HACSpec HStockCharts.podspec  //前面是本地Repo名字 后面是podspec名字

将HStockCharts推送到spec repo。执行pod repo update HACSpec,再cd 到本地repo文件夹/Users/hotacool/.cocoapods/repos/HACSpec,我们可以看到:

.
├── HStockCharts
│   └── 0.1.0
├── README.md

HStockCharts已从remote加入本地spec,版本号0.1.0。

  • 集成私有库
    现在我们可以在其他项目中,集成我们自己的私有库。Podfile加入:
pod 'HStockCharts'

运行pod install,报错[!] Unable to find a specification for HStockCharts
因为HStockCharts在我们自己的spec repo中,默认的source地址是cocoapods repo。Podfile头部加入:

source 'https://gitee.com/hotacool/HACSpec.git' #private spec repo

再次pod install,成功。
同样的,因为我们的spec是自建的,没有fork cocoapods的,所以不包含其他公共的私有库,如果你用到pod 'AFNetworking',Podfile也需要添加:

source 'https://github.com/CocoaPods/Specs.git'

至此,我们已经完整的建立了私有库,并加入私有的spec repo。但这个库什么都干不了,如果加入我们自己的代码,需要做更多的配置和处理。

私有库设置

上面的介绍,没有涉及功能代码,实际的私有库配置,往往会遇到各种问题。下面对遇到的具体问题,做一些对应。

  • 私有库依赖其他framework、library设置
    解耦出的私有库工程,往往有对某些系统或者第三方framework、.a静态库的依赖。所以需要对私有库的podspec进行设置,使其他工程Podfile集成私有库时,能够自动引入相关依赖。
    我们会对下面几个标签进行设置:
s.framework = 'SystemConfiguration','CoreData' //对系统framework的依赖
s.libraries   = 'c++','stdc++.6.0.9' //对系统.a的依赖,注意去掉lib前缀

s.ios.vendored_libraries = 'libXXX.a' //对第三方.a的依赖,注意路径,libXXX.a在.podspec目录下
s.ios.vendored_frameworks = 'XXX.framework' //对第三方framework的依赖

在项目的Pods工程的私有库target的build setting中,查看Other linker Flags,可以看到依赖的framework和.a:


1514193548251.jpg
  • 包含.a的私有库
    上面说了对第三方.a的依赖,可以通过s.ios.vendored_libraries来配置。公开的header files,按照官方说明,可以再s.public_header_files中设置。但貌似没达不到效果。目前我是通过s.source_files,将.h头文件统一放在s.source_files目录下输出。
    另一个问题,直接对包含.a的私有库使用pod lib lint来validation是可以通过的,但pod repo push验证会出错会报错。报错内容类似:
- ERROR | [iOS] unknown: Encountered an unknown error (The 'Pods-App' target has transitive dependencies that include static binaries: (/private/var/folders/vx/466sl6v913z1xvbssfgpnjb00000gn/T/CocoaPods-Lint-20171220-2701-11qtxh8-MarketModule/Pods/XXXLib/libXXX.a)) during validation.

虽然这并不影响Pod的使用,但是验证是无法通过的。可以通过 --use-libraries 来让验证通过:

pod repo push HACSpec MarketModule.podspec --allow-warnings --use-libraries

还有一个类似问题,如果在工程Podfile中使用use_frameworks!,会报错:

[!] The 'Pods-Venus' target has transitive dependencies that include static binaries: (/Users/hotacool/work/0-Venus/0-Venus/Pods/SZYDataSource/libMApi.a)

这里一个可行的办法,只能注释掉'use_frameworks!'。参考链接

  • 通配符地址匹配报错
    可能会出现类似报错:
- ERROR | [iOS] file patterns: The `source_files` pattern did not match any file.

一般是文件路径无法匹配造成。例如:

s.source_files = ['HStockCharts/Classes/**/*.{c,h,hh,m,mm,cpp}' //可以匹配HStockCharts/Classes/A/A.m,但无法匹配HStockCharts/Classes/A.m。
s.source_files = ['HStockCharts/Classes/**/*.{c,h,hh,m,mm,cpp}', 'HStockCharts/Classes/*.{c,h,hh,m,mm,cpp}'] //可以实现上述匹配
  • 私有库对私有库的依赖
    私有库B需要依赖私有库A,可以在B.podspec中设置:
s.dependency 'A'

如果执行pod lib lint 或者pod repo push 会报错找不到A。这是肯定的,默认的spec repo是cocoapods的公共库,所以需要对source单独设置:

pod lib lint --sources='https://gitee.com/hotacool/HACSpec.git'
pod repo push HACSpec B.podspec --sources='https://gitee.com/hotacool/HACSpec.git'
  • 添加资源文件
    有以下几种添加资源方法:
// 1.默认方式,cocoapods会将匹配路径'HStockCharts/Assets/*.png’下资源打包为名称HStockCharts的bundle,代码中通过bundle名来查找资源。
  s.resource_bundles = {
    'HStockCharts' => ['HStockCharts/Assets/*.png']
  }
// 调用方式:
NSBundle *bundle = [NSBundle bundleForClass:[MYSomeClass class]];
NSURL *bundleURL = [bundle URLForResource:@"HStockCharts" withExtension:@"bundle"];
NSBundle *resourceBundle = [NSBundle bundleWithURL: bundleURL];
UIImage *img = [UIImage imageNamed:icon inBundle:bundle compatibleWithTraitCollection:nil];
// 或者
UIImage *image = [UIImage imageNamed:@"HStockCharts.bundle/imagename"];

// 2.资源会在打包的时候直接拷贝的main bundle中,可能会和其它资源产生命名冲突
spec.resources = ["Images/*.png", "Sounds/*"]

// 3.把资源都放在bundle中,然后打包时候这个bundle会直接拷贝进app的mainBundle中。使用的时候在mainBundle中查找这个bundle然后再搜索具体资源
spec.resource = "Resources/MYLibrary.bundle"
//调用方式:
NSURL *bundleURL = [[NSBundle mainBundle] URLForResource:@"JZShare" withExtension:@"bundle"];
    NSBundle *bundle = [NSBundle bundleWithURL:bundleURL];
    UIImage *img = [UIImage imageNamed:icon inBundle:bundle compatibleWithTraitCollection:nil];
  • pod lib lint 和 pod spec lint
    两者都是对私有库进行validation。不过区别是:
pod lib lint是只从本地验证你的pod能否通过验证
pod spec lint是从本地和远程验证你的pod能否通过验证

所以如果你使用pod spec lint,需要保证remote及时更新,否则会发现本地已经做了修改,但一直无法validation成功...

  • 更新维护podspec
    在开发初始阶段,可能会频繁对podspec进行更改。在pod repo push后,记得及时pod repo update specName,否则可能拉取不到最新的私有库配置。

  • mrc文件设置
    某些文件需要设置mrc,podspec可以如下设置:

  non_arc_files = 'MyLib/Classes/xxx.m'
  s.exclude_files = non_arc_files

  s.subspec 'no-arc' do |sp|
    sp.source_files = non_arc_files
    sp.requires_arc = false
  end

but,不知是否是cocoapods的版本原因(1.3.1),采用这种方式我仍旧无法通过验证。通过--no-clean,进入验证的APP工程,发现私有库下的编译文件只有non_arc_files文件。
怀疑是否默认使用no-arc 的subspec,之后参照链接,设置default_subspec和subspec。但仍旧无法验证通过。
最后,只能通过将requires_arc设为mrc,单独添加arc配置,来实现对mrc文件的编译。如下:

#source_files需要包含mrc和arc全部文件路径
s.source_files = ["xxx/MRC/*.{c,h,hh,m,mm,cpp}", 'xxx/Classes/**/*.{c,h,hh,m,mm,cpp}', 'xxx/Classes/*.{c,h,hh,m,mm,cpp}'] 
s.requires_arc = false
s.requires_arc = ['xxx/Classes/**/*.{c,h,hh,m,mm,cpp}', 'xxx/Classes/*.{c,h,hh,m,mm,cpp}']
  • pod 本地repo缓存清理
    私有库开发阶段,会经常修改文件,这时候极易出现remote上已更新,但本地pod install/update 的私有库还是老版本的情况。这往往是因为pod本地缓存导致,需要更新本地缓存:
//1. 更新本地spec
pod repo update xxxSpec
//2. 去本地pods缓存文件夹(~/Library/Caches/CocoaPods/Pods/Release),删除需要更新的库
open ~/Library/Caches/CocoaPods/Pods/

3. 项目集成私有库

私有库已搭建完成,私有库的集成方式与cocoapods公共库集成方式无异,只需按照上述,在Podfile的source加入私有spec repo地址。无需赘述。
这里提一下本地相对路径的集成方式。我们可以通过pod xxx,直接从remote clone私有库到项目中。也可以通过pod 'HStockCharts', :path => '../'的方式,将本地库链到工程中。对于需要开发的各个私有库,可以将其clone到本地,然后本地相对路径的方式链接到工程中,对私有库进行修改,然后push到对应私有库。或许可以以此来解耦复杂工程,结合脚本,搭建环境,完成业务的独立开发,这也是目前我做的事情。

4. 总结

这里总结了一下基于cocoapods的私有库搭建,归纳了一些基本的方法,记录了一些遇到的问题。因为需求不大,涉及深度有限。如在后续改造优化过程中有更多的问题出现,也在此同步更新。

参考资料:
使用Cocoapods创建私有podspec
Cocoapods使用私有库中遇到的坑

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容