想搞一个组件化的项目,所以先好好看下如何使用 cocoapods 创建一个私有库
之前的文章中我提到过,加速 pod install
的一个方法是 pod install--no-repo-update
,下面就要解释一下这个后缀是要干什么。
我们首先进入 Cocoapods 的本地目录:
1 |
$cd ~/.cocoapods/repos/master |
可以发现这其实是一个 git 仓库:
1 2 3 4 |
$ git remote -v origin https://github.com/CocoaPods/Specs.git (fetch) origin https://github.com/CocoaPods/Specs.git (push) |
进入 Specs
文件夹,会有很多歌0~f的文件夹嵌套,一层一层点下去,可以看到可以看到一些熟悉的第三方库,随便选一个点进去,是各个版本的文件夹,再随便找个文件夹进入,是一个json文件。
打开这个json文件,里面记录了这个版本的库的一些信息,包括这个库的远程仓库的地址:
所以我们就能大概明白了,每次我们 pod install
后,就会在这个 Specs
下查找对应的库的对应版本,然后找到相应的远程仓库,去远程仓库拉取代码。
在 install 前,我们要编写一个 Podfile
。整个依赖安装大概有四个部分:
Pods.xcodeproj
工程Podfile 是 ruby 的语法,在 install 过程中,执行 Podfile 文件,将会获取到要依赖的库以及库的版本号。然后在本地的 Specs
中进行比对,找到对应的远程仓库的地址。
大部分的依赖都会被下载到 ~/Library/Caches/CocoaPods/Pods/Release/
这个文件夹中,然后从这个这里复制到项目工程目录下的 ./Pods
中。
如果 Specs
中如果没有找到 Podfile
中解析的库,就会报错。那么如果我们很久没有手动更新 Spec
了呢?不用担心,每次 Pod install
的时候,都会自动先去更新一下 Specs
:pod repo update
,当网络不好的时候就会很慢,所以才有了 pod install --no-repo-update
,在 install 时,不更新 Specs
的命令。
pod install
完成后,会自动生成一个 Podfile.lock
,用来锁定版本。我们不应该将其添加到 .gitignore
中去。只有在 pod update
后,才会自动更新 Podfile.lock
。
CocoaPods 通过组件 CocoaPods-Downloader 已经成功将所有的依赖下载到了当前工程中,这里会将所有的依赖打包到 Pods.xcodeproj
中。主要做了几件事:生成 Pods.xcodeproj
工程;将依赖中的文件加入工程;将依赖中的 Library 加入工程;设置目标依赖(Target Dependencies)
这几件事情都离不开 CocoaPods 的另外一个组件 Xcodeproj,这是一个可以操作一个 Xcode 工程中的 Group 以及文件的组件,我们都知道对 Xcode 工程的修改大多数情况下都是对一个名叫 project.pbxproj
的文件进行修改,而 Xcodeproj 这个组件就是 CocoaPods 团队开发的用于操作这个文件的第三方库。
最后的这一部分与生成 Pods.xcodeproj
的过程有一些相似。首先会创建一个 workspace,之后会获取所有要集成的 Target 实例,然后将每一个 Target 加入到了工程,使用 Xcodeproj 修改 Copy Resource Script Phrase
等设置,保存 project.pbxproj
,整个 Pod install 的过程就结束了。
CocoaPods 都做了什么?
深入理解 CocoaPods
:path
如果是我们自己开发的私有库,并且在开发阶段的情况下,可能就希望开发模式进行引用,则可以使用path参数::path => '~/Documents/AFNetworking'
1 2 3 4 |
target ‘Mike’ do pod 'RNFS', :path => '../node_modules/react-native-fs' pod 'React', :path => '../node_modules/react-native' end |
cocoapods 会自动到 path 所在文件夹内查找 RNFS.podspec
以及 React.podspec
文件,然后根据这两个文件内提供的信息创建库(下面介绍如何创建库的时候会说到)。
platform
这个参数是只依赖的库希望在哪个平台被编译。直接使用platform :ios, '9.0'
。说希望采用iOS9.0的进行编译。
use_frameworks!
这个指明编译成动态库,而不是静态库,特别是在使用Swift库的过程中,特别需要使用这句。不过他会把所有项目的编译动态库,这一点有点不好。不过在使用Swift库的过程中就没办法了。
source
这个参数是指Cocoapods
从哪些仓库(Spec
)中获得框架的源代码,如果在结合使用开源库以及自己私有库的情况下,这个参数还是非常有意义的。在用到自己私有库的情况下只需要在Podfile
文件开头列出你需要引用库的所有仓库地址即可。
1 2 |
source 'https://github.com/artsy/Specs.git' source 'https://192.168.0.90:8888/MySepcs/Specs.git' |
最后一个官方 demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
# open source source 'https://github.com/CocoaPods/Specs.git' # my work source 'https://github.com/Artsy/Specs.git' target 'App' do pod 'Artsy+UIColors' pod 'Artsy+UIButtons' pod 'FLKAutoLayout' pod 'ISO8601DateFormatter', '0.7' pod 'AFNetworking', '~> 2.0' target 'AppTests' do pod 'FBSnapshotTestCase' pod 'Quick' pod 'Nimble' end end |
首先要注册一个 CocoaPods 账号,在终端使用 pod trunk
命令注册,之后会有一封确认邮件,激活账号:
1 |
$pod trunk register [email protected] 'Zachary' --verbose |
激活成功后,再到终端输入,可以看到注册信息:
1 |
$pod trunk me |
1 2 3 4 5 6 7 |
ZacharydeMacBook-Pro:1.0.0 zachary$ pod trunk me - Name: Zachary - Email: [email protected] - Since: June 29th, 21:08 - Pods: None - Sessions: - June 29th, 21:08 - November 4th, 21:09. IP: 140.207.1.250 |
这一部分之前的文章打包静态库里有较为详细的说明。为方便查看,复制了过来。
创建工程
只需要输入 pod 的 lib
命令即可完成初始项目的搭建:
1 |
pod lib create StaticWithCocoapods |
输出指令后,会提示确认五个问题,按需求回答即可:
稍等片刻,就会自动生成一个工程。
配置信息
在项目目录下有一个 xxx.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 |
Pod::Spec.new do |s| s.name = 'StaticWithCocoapods' s.version = '0.1.0' s.summary = 'A short description of StaticWithCocoapods.' # 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/zhang759740844' s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { 'Zachary' => '[email protected]' } s.source = { :git => '/Users/zachary/Desktop/StaticWithCocoapods', :tag => s.version.to_s } s.ios.deployment_target = '8.0' s.source_files = 'StaticWithCocoapods/Classes/**/*' s.resources = ['Images/*.png','Sounds/*'] s.resource_bundles = { 'StaticWithCocoapods' => ['StaticWithCocoapods/Assets/*.png'] } s.public_header_files = 'StaticWithCocoapods/Classes/**/*.h' s.frameworks = 'UIKit', 'MapKit' s.dependency 'AFNetworking', '~> 2.3' s.dependency 'SVProgressHUD' s.subspec 'Aswift' do |a| a.source_files = 'A_swift/A_swift/**/*' end end |
其中要说明的是:
source 表示从哪里拉取代码。可以填写远端 git 仓库,也可以是像我写的那样的本地 git 仓库。如果 Podfile 中指定了 :path
, 那么,就会在指定的路径查找,source 这里写啥都无所谓。
依赖项不仅要包含你自己类库的依赖,还要包括所有第三方类库的依赖,只有这样当你的类库打包成 .a 或 .framework 时才能让其他项目正常使用。
source_file 路径中出现的通配符 *
表示匹配任意字符, **
表示匹配所有当前文件夹和子文件夹。
s.resources 中的资源文件如果是静态库就直接放在 mainBundle 中,如果是动态库就放在动态库所在 bundle 中。这样文件多了可能会重名,一般的做法是讲资源文件先自己打个 bundle,然后再通过 s.sources 引入。另外,Cocoapods 提供了 s.resource_bundles,可以将资源文件按需打成不同的 bundle。比如上面就是将 StaticWithCocoapods/Assets/*.png
目录下的所有图片打成 StaticWithCocoapods
的 bundle。
这里的 dependency 一般情况下是 cocoapods 的官方库中的。当然你也可以给自己创建的库添加自己的私有库,同样是直接写在 dependency 里。但是由于 dependency 表示编译器需要这样的一个依赖,没有指定从哪里获取,因此,添加私有库的时候要在使用这个库的工程的 Podfile 中添加你私有库的远端地址。这样的话,在下载 s.dependency 时,官方库找不到的情况下,就会到私有库的远端地址中查找。如果都找不到就会 pod install
失败。有一种情况不用添加私有库的远端地址,也可以 install 成功。那就是在 Podfile 中添加这个私有库,并为这个私有库设置本地路径 :path
。由于 s.dependency 其实是创建了一个外部依赖,所以只要外部存在这个私有库,s.denpendency 就会自动链接过去。这一点非常非常重要。我们平时用 pod 实现组件化就是这么做的。比如你的主工程同时依赖于私有库 pod A,pod B,以及本地私有库 pod C。与此同时,pod A 和 pod B 也依赖于 pod C。这个时候是不是一定要将 pod C 放到远端,然后才能成功设置 pod A 和 pod B 呢?肯定不用。因为 pod C 已经通过本地路径引入: pod 'C', :path => '../'
。所以 pod A 和 pod B 的 s.dependency 就不需要再到远端去查找 pod C 了。
什么时候用 s.subspec 呢?一般一个大的项目写成pod的时候,它可能会分为多个subspec,这样的话当你用一个庞大的库时,只需要其中的一小部分,那么就可以使用其中的某个subspec了。
我们拿AFNetworking.podspec来看:
1 |
|
pod ‘AFNetworking/Reachability’
或者
pod ‘AFNetworking’,:subspecs=>[‘Reachability’,’Security’]
1 |
|
所以可以把subspec当做一个小型的pod来看。来看一下用 subspec 后,在 pod install 后得到如下文件结构:
(上面说的 你的库 指的是你在做的库,私有库 指的是在你私有远端仓库而不是在官方仓库里的库)
添加文件
向 sources_files 和 public_header_files 以及 resource_bundle 中添加图片和类文件。在 demo 的文件夹下执行 pod install
。现在打开 demo 工程,可以看到创建的 StaticWithCocoapods
库的文件结构如下图:
现在工程里是带有 demo application 的,不过不用担心,在.podspec 中已经设置了源文件的目录,不会把 demo 中的各种测试文件也打包进去的。
不要在意上面图片上的图片路径 和 .podspec 中设置的 s.resource_bundles 路径不一致。这里的 Resources 看似是个文件夹,其实是一个 group,不是实际文件路径的地址。本例中实际的地址就是
['StaticWithCocoapods/Assets/*.png']
这个路径。
加载图片资源的问题
到这里一切正常,也可以使用 SVProgressHUD
,但是当我想用 [UIImage imageNamed:[[[NSBundle mainBundle] pathForResource:@"StaticWithCocoapods" ofType:@"bundle"] stringByAppendingString:@"/author.png"]]
加载图片资源文件时,一直返回 nil
。最后在 Stackoverflow 的一个评论里总算找到了解决方法[Cocoapods]:Resource Bundle not accessbile
原先 demo 中 prdfile 的内容如下:
1 2 3 4 5 6 7 8 9 10 11 |
use_frameworks! target 'StaticWithCocoapods_Example' do pod 'StaticWithCocoapods', :path => '../' target 'StaticWithCocoapods_Tests' do inherit! :search_paths pod 'FBSnapshotTestCase' end end |
现在要删除 use_frameworks!
以及其相关内容,变成这样:
1 2 3 |
target 'StaticWithCocoapods_Example' do pod 'StaticWithCocoapods', :path => ‘../‘ end |
再次尝试加载图片,可以得到正确结果. ^_^。但是为什么会这样?
use_frameworks!
的添加与否,表示将 pod 中的库打包成静态库.a 还是动态库.framework。其实就是图片在盗宝成静态库或者动态库的时候所处的 bundle 不同。
如果打包成了 .a,那么编译的时候库里的所有文件其实是被打包在主工程下的,即 mainBundle 中,即可像通常那样在 mainBundle 中获取 StaticWithCocoapods.bundle 中的图片。
如果打包成了 .framework ,动态库里的文件不再被打包在主工程下了,而是在动态库自己的 bundle 中,比如本例中的动态库 bundle 路径如下:
此时就不能再在 mainBundle 中,而需要在动态库中的 bundle 下找文件了。我们可以学习下 SVProgressHUD
的做法,通过 [NSBundle bundleForClass:[SVProgressHUD class]]
拿到 SVProgressHUD 文件所在的 bundle,也就是资源文件所在的SVProgressHUD.Bundle
所在的 bundle,然后获取图片:
1 2 3 4 |
NSBundle *bundle = [NSBundle bundleForClass:[SVProgressHUD class]]; NSURL *url = [bundle URLForResource:@"SVProgressHUD" withExtension:@"bundle"]; NSBundle *imageBundle = [NSBundle bundleWithURL:url]; UIImage* infoImage = [UIImage imageWithContentsOfFile:[imageBundle pathForResource:@"info" ofType:@"png"]]; |
测试类库
在 demo application 中,如果把自己的库使用 pod 引入,注意要修改 Podfile 中库的路径为本地 podspec
所在的路径,不然每次都要从远端拉了。另外还有一个问题是这样每次更新库里的内容,运行的时候都要先 pod install 一下,这样非常的麻烦。
因此,我们可以直接将库里的文件添加到 demo application 中,同时要修改 Podfile,使其不要 pod 自身,而是 pod 其依赖的其他类库。这样做就是把类库的文件放到主工程下,就不用每次都 pod install 更新修改了。比如:
1 2 3 4 5 6 7 8 9 |
# 原来可能是这样的 target 'StaticWithCocoapods_Example' do pod 'StaticWithCocoapods', :path => ‘../‘ end # 现在要把依赖 StaticWithCocoapods 的类库加上,删除自己: target 'StaticWithCocoapods_Example' do pod 'SVProgressHUD' end |
不用担心这样做不会产生任何问题。demo 中的文件依赖的改动并不影响 .podspec
中的设置,只要文件还是放在 .podspec
的指定路径下就行。发布类库的时候还是按照 .podspec
进行的,所以没有任何影响。
但是要注意一点,虽然你把文件放在主工程下编译,但是代码中获取资源文件的时候绝对不能偷懒使用 mainBundle 加载。因为别人可能将你发布的库打成动态库放到自身的工程中,这就会造成上面加载图片资源一样的问题。所以加载资源文件的时候一定要用上面说的通用的方法。
提交代码
0.1.0
设置好后如图:
这里要注意,tag 一定要打上,版本控制的时候就是以 tag 来辨别的。
验证类库
开发完成静态类库之后,需要运行pod lib lint验证一下类库是否符合pod的要求。添加 --allow-warnings
忽略警告:
打包库
如果你要发布到 cocoapds,那么这个操作是不必要的。但是如果你想直接获得一个动态或者静态库,那么可以手动打包这个库。
打包库需要一个 cocoapods 的插件 cocoapods-packager来完成类库的打包。
在终端执行以下命令,安装插件:
1 |
sudo gem install cocoapods-packager |
在目标文件夹内执行以下命令,完成打包:
1 |
pod package StaticWithCocoapods.podspec --force |
打包成 .framework
,也可以用 --library
打包成 .a
。
现在在目标文件夹下就会多出一个 StaticWithCocoapods-0.2.0
目录,里面是打包好的 framework 。
虽然 s.dependency 只是创建了一个链接,但是 package 会下载 dependency 的代码,并将其打包进你的库中
最后一步:发布。在仓库目录下执行:
1 |
pod trunk push xxxxx.podspec |
上述命令将会更新本地的 repo 目录,然后将其提交到Cocoapods
官方的仓库里去。
提交完成后,我们可以通过一下命令查询是否上传成功:
1 |
pod search xxx |
如果是多人开发,希望将其他人也加入到项目中去的话,可以通过以下方式:
1 |
pod trunk add-owner '项目名' '邮箱' |
当版本更新后,需要做的就是改变 xxx.podspec
中的版本号,然后重复上一步的步骤发布即可。
至于删除废弃等操作可以详见pod trunk
的 help中:
CocoaPods公有仓库的创建
Cocoapods系列教程(二)——开源主义接班人
之前说到,在~/.cocoapods/repos/
目录下有一个 master 文件夹,对应着 cocoapods 官方的一个版本库,保存着各个第三方库的索引。我们要创建一个私有库,就得先创建一个私有的版本库来存放这些私有库的索引。
我们先在远端创建一个空的仓库test,然后再终端执行:
1 |
$ pod repo add test https://git.oschina.net/baiyingqiu/test.git |
这个仓库就被 clone 了下来,作为本地的版本目录了,可以进入~/.cocoapods/repos/
文件夹查看:
和上面公有的无二致。将本地代码推送到远程,并打上 tag。
需要明确,cocoapods 是通过 tag 来区分版本信息的,而不是 branch。也就是说,不论你的 tag 是打在 master 还是 develop 亦或任意一个其他分支,cocoapods 都能够正确获取该 tag 所处的 commit 的文件。
所以你的私有库其实不用再开一个新的 repo 去存储,可以直接在当前工程下开发。新开一个私有库的分支,专门开发私有库也是没有问题的。
将我们私有库的描述信息 push 到刚才的版本库中:
1 |
$ pod repo push test xxx.podspec --verbose --allow-warnings |
要加上 --allow-warnings
不然还是不能通过。
现在就可以通过 pod search xxx
来搜索了。
使用私有库的时候要在使用的工程的 Podfile
中加上私有库的版本库的路径,若有还使用了公有的pod库,需要把公有库地址也带上。例子:
1 2 3 4 5 6 7 8 |
source ‘https://github.com/CocoaPods/Specs.git’ source ‘https://github.com/zhang759740844/test.git’ platform :ios, '8.0' target ‘Mike’ do pod 'xxx' end |
现在就可以 pod install
了。
转载自:https://zhang759740844.github.io/2017/06/30/cocoapods%E5%88%9B%E5%BB%BA%E4%B8%80%E4%B8%AA%E5%BA%93/