0%

Cocoapods二进制化方案

接上文SVN+Cocoapods, 使用SVN存放Specs索引库有一个致命缺陷,如果开发组件库的过程中又在podspec文件里使用dependency依赖了其他私有库, 那么之后在使用pod package打包时会报错无法找到依赖的私有库。这是因为pod package默认搜索官方索引库, 而且手动指定–spec-sources并不支持SVN路径。

为解决打包问题,代码依旧托管到SVN,改将Specs索引库托管到第三方Git。 详细操作流程见下文。

创建远程私有库

这里使用码市(https://coding.net)建立远程私有库。注册账号并新建项目:

绑定远程私有库

要让pod search也能搜索到我们自己开发的组件库, 与cocoapods官方的公有索引库原理类似, 也需要将刚刚创建的远程私有库同步到本地, 打开终端, 输入:

pod repo add ComponentSpecsGit https://git.coding.net/mangox/ComponentSpecs.git

命令格式为:

pod repo add 本地私有库名称 远程私有库地址

同步完成后, 输入以下命令查看本地所有的cocoapods索引库:

pod repo

结果如图:

更多私有库相关介绍参见官方文档:Private Pods

创建SVN代码目录

SVN代码托管目录如图:

svn catalogue

目录说明如下:

  • dev: 组件库源码开发目录, 在此目录下进行源码开发, 以及开发完成后的打包工作;
  • trunk:用于组件库发布, 此目录下的工程只引用dev打包的framework, 不涉及源码;
  • tags:组件库拉取目录,保存组件库的各个版本,供组件使用者拉取使用;

具体每个目录的使用详见后文。

创建本地组件

使用下面的命令创建一个登录组件:

pod lib create XBLoginUIModule

如图, 可按照个人开发需求对组件进行配置:

pod lib create

由于这里使用SVN进行代码管理,而通过上面命令下载的pod模板时实际上使用的是git, 建议删除掉以下三个隐藏文件:.git,.gitignore.travis.yml。 并将项目目录调整为下图所示:

pod lib create SVN

创建完成后,将XBLoginUIModule目录整体提交到SVN。

更多pod lib create介绍参见官方文档Using Pod Lib Create

编辑podspec

打开dev目录下的XBLoginModule.podspec文件,推荐使用Sublime Text,右下角选择语言为Ruby,会出现语法高亮效果。

这里对几个关键配置进行编辑说明:

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
Pod::Spec.new do |s|
#项目名称, 和文件名保持一致即可
s.name = 'XBLoginUIModule'
#项目版本号
s.version = '0.1.0'
#项目简介
s.summary = '秀吧登录UI组件.'

#项目详细介绍
s.description = <<-DESC
秀吧登录UI组件,可通过该组件集成不同样式的登录UI界面.
DESC

#项目主页, 须为可访问的URL
s.homepage = 'http://devzhang.cn'
#开源许可证
s.license = { :type => 'MIT', :file => 'LICENSE' }
#项目作者
s.author = { 'devzhang' => 'zhangmiao@xiu8.com' }
#项目地址,此处为SVN上开发目录dev地址
s.source = { :svn => 'svn://svn.xiu8.com/mobileLive/front/IOS/ComponentLibrary/ComponentProject/XBLoginUIModule/dev'}

#项目支持的iOS最低版本
s.ios.deployment_target = '8.0'

#项目需要包含的源文件地址
s.source_files = 'XBLoginUIModule/Classes/**/*'

#项目资源文件地址
# s.resource_bundles = {
# 'XBLoginUIModule' => ['XBLoginUIModule/Assets/*','XBLoginUIModule/Classes/*.xib']
# }

#项目public头文件地址
# s.public_header_files = 'Pod/Classes/**/*.h'
#项目所依赖的系统框架
s.frameworks = 'UIKit', 'Foundation'
#项目所依赖的系统lib库
# s.libraries = 'xml2', 'z'

#引用自己或第三方的framework或.a文件, 路径为从.podspec所在目录为根目录的相对路径
# s.ios.vendored_frameworks = "xxx/**/*.framework"
# s.ios.vendored_libraries = "xxx/**/*.a”

#项目依赖的开源库或私有库
# s.dependency 'AFNetworking', '~> 3.1'
# s.dependency 'YYModel'

#由于私有库依赖与开源库略有不同, 这里加入已开发完成的私有库作为示例
s.dependency 'XBHTTPService', '~> 1.0.0'

end

编辑完成后,将改动提交到SVN。

更多podspec介绍参见官方文档:Podspec Syntax Reference

安装本地组件依赖

在开始具体的组件库开发之前,需要对Example工程安装pod依赖。

cd到Example目录下, 执行:

pod install

此时会出现一个错误:

pod lib create SVN

这是因为我们在podspec文件中依赖了私有库XBHTTPService, 因此在使用时需要告诉pod在哪个地方拉取我们已开发完成的私有库。

打开Podfile文件, 在最上部添加两行:

source 'https://github.com/CocoaPods/Specs.git'
source 'https://git.coding.net/mangox/ComponentSpecs.git'

上面的代码指定了pod的索引库地址,前者是cocoapods公有库, 后者是我们自己建立的私有库。

然后重新执行pod install, 会报错:

pod lib create SVN

这是由于私有库XBHTTPService被打包成了一个静态库,继续打开打开Podfile文件,在指定sources之后添加:

1
2
pre_install do |installer| Pod::Installer::Xcode::TargetValidator.send(:define_method, :verify_no_static_framework_transitive_dependencies) {}
end

注意, 上面的代码根据cocoapods版本不同略有差异,上述写法针对cocoapods 1.3.1或以上版本。

再次执行pod install, Pod installation complete!

开发本地组件

打开XBLoginUIModule.xcworkspace, 查看到在podspec文件中指定的依赖, Command+B编译, 如果出现错误的话可能是上面的某个步骤出现了问题, 可以根据错误描述进行修改。

本地组件的开发是在Pods工程下的Development Pods目录下进行, 删除自带的ReplaceMe.m文件, 然后在目录下新建文件进行开发。

注意,每次只要修改了podspec文件,都必须cd到Example目录下执行命令: pod update

校验本地组件

开发完成后,将本地dev目录提交到SVN,然后cd到dev目录下, 执行命令:

pod spec lint XBLoginUIModule.podspec --sources=https://github.com/CocoaPods/Specs.git,https://git.coding.net/mangox/ComponentSpecs.git  --use-libraries

pod spec lint

参数sources指定索引库地址, 参数–use-libraries表明依赖的库中包含静态库, 另外, 还可以加上参数--allow-warnings忽略警告, 否则存在任何warning的话也无法通过校验。

打包本地组件

通过校验之后, 可以将本地组件打包为framework。此时,需确保dev目录下所有改动均已提交到SVN。

cd到dev目录下, 执行命令:

pod package XBLoginUIModule.podspec --no-mangle --spec-sources=https://git.coding.net/mangox/ComponentSpecs.git,https://github.com/CocoaPods/Specs.git --force --exclude-deps

参数–no-mangle表明本地组件依赖了静态库, 参数–spec-sources指定索引库地址, –force表明强制覆盖上一次打包生成的文件(同版本), –exclude-deps表明不包含所依赖的符号库,可以减少framework的大小,不加此参数的话如果主工程引入了多个私有库,会抛出duplicate symbol错误。

打包完成后, 可以在dev目录下找到XBLoginUIModule.framework,如图所示:

pod spec lint

发布本地组件(framework)

要以framework的形式发布本地组件, 首先需要在trunk目录下使用pod lib create命令创建一个同名的组件库。 然后将上面打包好的framework复制到trunk->XBLoginUIModule目录下,
并删除掉默认添加的ReplaceMe.m ,最终目录结构如图所示:

trunk

trunk下的podspec文件大部分与dev目录下相同,不同处参见注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Pod::Spec.new do |s|
s.name = 'XBLoginUIModule'
s.version = '0.1.0'
s.summary = '秀吧登录UI组件.'
s.description = <<-DESC
秀吧登录UI组件,可通过该组件集成不同样式的登录UI界面.
DESC

s.homepage = 'http://devzhang.cn'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'devzhang' => 'zhangmiao@xiu8.com' }
s.source = { :svn => 'svn://svn.xiu8.com/mobileLive/front/IOS/ComponentLibrary/ComponentProject/XBLoginUIModule', :tag => s.version.to_s }

s.ios.deployment_target = '8.0'

#由于不包含任何源码,所以也不需要指定s.source_files

#本地引用的framework
s.ios.vendored_frameworks = 'XBLoginUIModule/XBLoginUIModule.framework'

#若是打包发布的framework还依赖其他第三方, 则这里也需要添加依赖
s.dependency 'XBHTTPService', '~> 1.0.0'

end

别忘了,要想使用Example工程还需要在Podfile中加入:

1
2
3
4
5
source 'https://github.com/CocoaPods/Specs.git'
source 'https://git.coding.net/mangox/ComponentSpecs.git'

pre_install do |installer| Pod::Installer::Xcode::TargetValidator.send(:define_method, :verify_no_static_framework_transitive_dependencies) {}
end

cd到Example目录下, 执行pod update命令, 这里其实可以加上--no-repo-update参数避免每次检测cocoapods的同步。

编译Example工程, 编译成功说明没有错误可以发布了, 提交trunk目录改动到SVN。

在SVN中在trunk的基础上打出与podspec中版本号相同的tag:

pod spec lint

tag完成后,cd到trunk目录下,执行命令:

pod repo push ComponentSpecsGit XBLoginUIModule.podspec

pod spec lint

发布成功后可以在之前coding.net上建立的索引库中看到。

使用已发布的组件

发布完成后, 可以通过pod search命令搜索刚刚发布的私有组件库:

pod spec lint

如果提示:

[!] Unable to find a pod with name, author, summary, or description matching `XBLoginUIModule`

可以使用命令清除cocoapods索引文件后重新尝试:

rm ~/Library/Caches/CocoaPods/search_index.json

其他常见问题

加载资源bundle

如果开发的私有库中使用了xib或者图片资源, 开发时(dev目录)就需要在podspec文件中指定:

1
2
3
4
#项目资源文件地址
s.resource_bundles = {
'XBLoginUIModule' => ['XBLoginUIModule/Assets/*','XBLoginUIModule/Classes/*.xib']
}

cocoapods会在framework内建立一个XBLoginUIModule.bundle, 包含引入的xib和图片资源。

开发完成并打包后的framework结构如图:

framework struct

这里明明是存在我们打包包含的bundle资源文件的,然而在实际运行Example项目(trunk目录)时,查看下图所示目录:

framework struct

bundle消失了!!!, 这是如果尝试使用bundle的xib文件, 会直接crash。

要解决这个问题, 可以将framework中的bundle文件复制到framework同级目录下:

framework struct

然后编辑podspec文件,增加如下配置:

1
2
3
# 再次引入资源包, 否则系统不会将framework内部的bundle实际拷贝到项目中
s.resource = 'XBLoginUIModule/XBLoginUIModule.bundle'

再次执行pod update, bundle成功引入项目。

非ARC文件

在实际开发私有库的过程中可能会遇到单独指定某个文件为MRC的情况,可以通过建立subspec的方式解决:

1
2
3
4
5
6
7
8
9
#这里可以不指定.h文件
non_arc_files = 'XBSecurityService/Classes/GBEncodeTool/GTMBase64/GTMBase64.m','XBSecurityService/Classes/GBEncodeTool/GTMBase64/GTMBase64.h'
s.exclude_files = non_arc_files

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

效果如图:

framework struct

其中GTMBase64.m已经被指定为-fno-objc-arc

symbol not found

如果开发私有库的过程中依赖或使用了第三方的静态库, 运行Example工程时可能会出现symbol找不到的情况, 打开Pod工程的Build Settings, 修改Mach-O TypeStatic Library