cocoapods进阶
cocoapods 安装更新
安装:
$ sudo gem update --system // 先更新gem,国内需要切换源
$ gem sources --remove https://rubygems.org/
$ gem sources -a https://gems.ruby-china.org
$ gem sources -l
\*\*\* CURRENT SOURCES \*\*\*
https://gems.ruby-china.org
$ sudo gem install cocoapods // 安装cocoapods
$ pod setup
更新到最新版:
sudo gem install cocoapods --pre
安装指定版本:
sudo gem install cocoapods --version 0.39.0
如果更新之后使用pod --version
查看版本还是老的版本那需要把老版本卸载了。
我们可以查看一下gem安装了cocoapods的哪些版本:
//gem是一种文件组织的包,一般的ruby的很多插件都有由这种各种的包提供。
$ gem list
*** LOCAL GEMS ***
cocoapods (1.1.0.rc.2, 1.0.1)
我们看到版本号后,可以卸载对应版本的cocoapods
:
sudo gem uninstall cocoapods 1.0.1
创建自己私有的spec仓库
Cocoapods 应用第二部分-私有库相关
如何创建私有 CocoaPods 仓库
制作自己的podspec文件
创建及提交
- 创建
podspec
文件
创建podspec文件
使用Cocoapods创建私有podspec
比如:pod spec create https://github.com/walkdianzi/DSCategories
-
如果
podspec
文件要推送到CocoaPod
官方库,需要使用pod trunk
命令。pod trunk
需要注册: -
podspec
格式转podspec.json
格式
pod ipc spec DSCategories.podspec > DSCategories.podspec.json
s.subspec
一般一个大的项目写成pod的时候,它可能会分为多个subspec
,这样的话当你用一个庞大的库时,只需要其中的一小部分,那么就可以使用其中的某个subspec
了。我们拿AFNetworking.podspec来看,比如只引入其中的Reachability
:
pod 'AFNetworking/Reachability'
或者
pod 'AFNetworking',:subspecs=>['Reachability','Security']
所以一般subspec
之间最好不要有互相依赖,不然的话,你用了其中一个subspec
,而它其中一个文件依赖了另一个你未引入的subspec
中的文件的话是会报错的。
如果有多个subspec
互相依赖的话,可以像AFNetworking.podspec
里这样写,UIKit
依赖于NSURLSession
:
s.subspec 'NSURLSession' do |ss|
//省略一大段代码
end
s.subspec 'UIKit' do |ss|
ss.ios.deployment_target = '7.0'
ss.tvos.deployment_target = '9.0'
ss.dependency 'AFNetworking/NSURLSession'
ss.public_header_files = 'UIKit+AFNetworking/*.h'
ss.source_files = 'UIKit+AFNetworking'
end
结论
所以可以把subspec
当做一个小型的pod来看,我们可以看一下pod AFNetworking
安装之后,Podfile.lock
中的pod
安装目录。可以看出那些subspec
也算是一个pod
。
PODS:
- AFNetworking (3.0.0):
- AFNetworking/NSURLSession (= 3.0.0)
- AFNetworking/Reachability (= 3.0.0)
- AFNetworking/Security (= 3.0.0)
- AFNetworking/Serialization (= 3.0.0)
- AFNetworking/UIKit (= 3.0.0)
- AFNetworking/NSURLSession (3.0.0):
- AFNetworking/Reachability
- AFNetworking/Security
- AFNetworking/Serialization
- AFNetworking/Reachability (3.0.0)
- AFNetworking/Security (3.0.0)
- AFNetworking/Serialization (3.0.0)
- AFNetworking/UIKit (3.0.0):
- AFNetworking/NSURLSession
DEPENDENCIES:
- AFNetworking (= 3.0)
SPEC CHECKSUMS:
AFNetworking: 932ff751f9d6fb1dad0b3af58b7e3ffba0a4e7fd
PODFILE CHECKSUM: f38d14cf91adf9e2024f841ce5336dae96aa6fa6
COCOAPODS: 1.2.0.beta.1
官方spec和pod库镜像
我们在使用pod的时候,不管是它去更新官方的spec仓库还是根据你需要的pod
去github
上下载源码,都会发现特别慢。github在国内没有服务器,虽然国内可以访问,但是速度慢的可怜。我发现即使翻墙了速度也不算快。
所以我就想把cocoapods
的官方仓库,和一些比较著名的pod库做一个镜像。但是严格的说也不能算是镜像,比如我cocoapods
的spec仓库就没有整个镜像过来,我只是把自己需要podspec
文件移到了一个自己的spec仓库里,毕竟官方的spec仓库里文件已经快多达百万个了,就算你在国内服务器下,也需要一些时间。
还有就是我自己没有架服务器来做一个定时更新的事。我现在是把spec仓库和一些自己项目中需要的第三方仓库放在coding
上面,然后用自己的电脑定时做一下和github
上的源码同步。所以非工作日或者节假日我没开电脑的情况下就不会更新了,如果有需要可以来私信我进行更新。
小型podspec
仓库地址:smallSpecs
spec仓库镜像
-
在
coding
上创建一个存放podspec
的仓库,用于之后从官方的spec仓库找到自己需要的podspec
文件上传到coding
的spec
仓库中。clone到本地,比如dasheng/coding/codingRepo
路径里。 -
clone
一份官方的spec
仓库到本地,然后遍历出自己需要的podspec文件,拷贝到自己的spec
仓库里(也就是上面提到的codingRepo)。 -
每一个
podspec
文件里都会有一个source:
"git": "giturl"
,来指向源码所在的地址,我们需要修改这个git所指向的地址为我们镜像到coding
的源码。如SDWebImage
的podspec
:
https://github.com/rs/SDWebImage.git
改为https://git.coding.net/dasheng/SDWebImage.git
以上遍历拷贝替换部分都是使用脚本完成的:脚本,如果要使用这个脚本需要修改里面的部分绝对路径。
pod第三方库镜像
-
clone
github
上的源码镜像:
git clone --mirror https://github.com/rs/SDWebImage.git
-
更新镜像:
git remote update
-
push
镜像到coding上创建的新仓库:
git push --mirror git@git.coding.net:dasheng/SDWebImage.git
这里也是现在一个source.txt
文本里列出需要镜像的第三方库,然后使用脚本完成以上步骤:脚本。跟上面一样部分路径需要替换。
注意错误:
我们在执行第三步git push --mirror codingUrl
的时候,会出现一大堆类似于以下的错误:
remote: Coding.net Tips : [Push to refs/pull dir is rejected!]
remote: error: hook declined to update refs/pull/1004/head
remote: Coding.net Tips : [Push to refs/pull dir is rejected!]
remote: error: hook declined to update refs/pull/1005/head
remote: Coding.net Tips : [Push to refs/pull dir is rejected!]
remote: error: hook declined to update refs/pull/1005/merge
remote: Coding.net Tips : [Push to refs/pull dir is rejected!]
其实这是镜像同步pull requst
的原因,我们在使用镜像的时候,镜像其实是从packed-refs
和FETCH_HEAD
文件中读取需要同步的heads
、tags
或pull requst
的(下图就是packed-refs文件的截图),但是我们只需要heads
、tags
,是不需要pull requst
的,在同步pull requst
的时候就会出现以上错误。具体可以参考Git Howto: Mirror a GitHub repo without pull refs。
其实这对你镜像同步的代码是不会有问题的,但是你看到这样一大堆错误提示是很烦的,所以我们通过修改config
文件去掉。
修改完config
之后,使用git remote update
更新后,就能看到FETCH_HEAD
中就没有关于pull requst
的信息了。但是packed-refs
中还有,我们手动删除包含refs/pull/
的行。然后你再git push --mirror codingUrl
就不会看到那些错误信息了。
[core]
repositoryformatversion = 0
filemode = true
bare = true
ignorecase = true
precomposeunicode = true
[remote "origin"]
url = https://github.com/rs/SDWebImage.git
fetch = +refs/*:refs/*
mirror = true
改为
[core]
repositoryformatversion = 0
filemode = true
bare = true
ignorecase = true
precomposeunicode = true
[remote "origin"]
url = https://github.com/rs/SDWebImage.git
fetch = +refs/heads/*:refs/heads/*
fetch = +refs/tags/*:refs/tags/*
mirror = true
pod相关命令
添加仓库
pod repo add DSSPecs https://github.com/walkdianzi/DSSpecs.git
删除仓库
pod repo remove DSSPecs
更新仓库
pod repo update DSSpecs
查看当前安装的pod仓库
pod repo
提交.podspec
pod repo push DSSpecs xxxx.podspec
验证.podspec文件
pod lib lint name.podspec
pod spec lint name.podspec
pod spec
相对于pod lib
会更为精确,pod lib
相当于只验证一个本地仓库,pod spec
会同时验证本地仓库和远程仓库。
--sources
当你的.podspec文件依赖其他私有库时要引入source
pod lib lint name.podspec --sources='https://github.com/walkdianzi/DSSpecs'
或者直接用仓库名,就是~/.cocoapods/repos/
文件夹下对应仓库的名字。
pod lib lint name.podspec --sources=DSSPecs,master
--no-repo-update
有时候当你使用pod update
时会发现特别慢,那是因为pod会默认先更新一次podspec索引。使用--no-repo-update
参数可以禁止其做索引更新操作。
pod update --no-repo-update
和git一样,本地有个pod repo,和github上的版本对应,如果你不想更新这个的话后面加上--no-repo-update
就可以了,但是这样会有个问题,如果github上pods的一些插件像AF有新版本了,你本地搜索的af还是旧版本如果用的新版本号是无法装配的,所以每隔一段时间我都会执行一下pod repo update
。
--verbose
意思是打印详细信息
--only-errors和--allow-warnings
--allow-warnings
是允许warning
的存在,也就是说当你在pod lib lint
验证podspec
的时候,如果不加这句,而你的代码里又一些警告的话,是验证不通过的。而加上这句话的话,有警告也能验证通过。
--only-errors
这句话是只显示出错误,就是你在验证的时候就算--allow-warnings
,但是那些warnings
也还是会打印出来和errors
混杂在一起,这会让你很难找error
。所以这里使用--only-errors
来只打印error
,不打印warning
。
--fail-fast
出现第一个错误的时候就停止
--use-libraries
pod在提交或验证的时候如果用到的第三方中需要使用.a静态库文件的话,则会用到这个参数。如果不使用--use-libraries
则会验证不通过。
但是比如你用swift
创建了一个pod
的话,你使用--use-libraries
就会报错,因为swift
开始,生成的就不是.a静态库了,它是不支持编译为静态库的,只能生成.Framework动态库。下面就是swift
的pod
使用--use-libraries
时的报错:
ERROR | [iOS] unknown: Encountered an unknown error (Pods written in Swift can only be integrated as frameworks; add use_frameworks! to your Podfile or target to opt into using it. The Swift Pod being used is: Socialite) during validation.
给Pod添加资源文件
在 podspec
中,我们利用 s.source_files
来指定要编译的源代码文件。但是一些资源文件如图片、nib、plist等要怎么办呢?这里有两种方式:s.resources
和s.resource_bundles
。
use_frameworks!
我们在Podfile
文件中是否加入use_frameworks!
,我们资源文件保存的位置也会有所不同。
我们使用这段代码
[NSBundle bundleForClass:<#ClassFromPodspec#>]
能够得到类对应所在的bundle
对象。
- 如果我们使用了
use_frameworks!
,那么 pod 将以framework
形式被链接,上面那段代码返回的bundle对象是framework
的bundle
,其实就是.frame
。如我测试代码中用模拟器测试打印出的bundle
对象为:/Users/dasheng/Library/Developer/CoreSimulator/Devices/F3DF178D-24E1-48AD-93FB-B729927702D9/data/Containers/Bundle/Application/871A55D3-A02E-412C-AA19-B46280E24FF8/test.app/Frameworks/PodName.framework
- 如果我们不使用了
use_frameworks!
,那么 pod 将以静态库(.a)的形式被链接,上面那段代码返回的bundle对象是client target
的mainBundle
,其实就是.app
。如我测试代码中用模拟器测试打印出的bundle
对象为:/Users/dasheng/Library/Developer/CoreSimulator/Devices/F3DF178D-24E1-48AD-93FB-B729927702D9/data/Containers/Bundle/Application/871A55D3-A02E-412C-AA19-B46280E24FF8/test.app
s.resources和s.resource_bundles
s.resources
和s.resource_bundles
的区别主要是后者在上面代码[NSBundle bundleForClass:<#ClassFromPodspec#>]
中得到的那个bundle
对象下面再生成一个.bundle
资源文件夹,相较于s.resources
直接平铺的方式更好的组织了文件,避免资源文件命名冲突。
举个例子比如我有一个pod
叫做PodTest
,使用s.resource_bundles
方式
s.resource_bundles = {
'PodTest'(.bundle文件名) => 'PodTest/Assets/*.{png,xib,plist}'
}
-
Podfile
使用use_frameworks!
编译后资源文件会在test.app/Frameworks/PodName.framework
下的PodTest.bundle
目录下。取值方式:
NSBundle *bundle = [NSBundle bundleForClass:<#ClassFromPodspec#>];
[UIImage imageWithContentsOfFile:[bundle pathForResource:@"PodTest.bundle/imageName@2x" ofType:@"png"]];
Podfile
不使用use_frameworks!
编译后资源文件会在test.app
下的PodTest.bundle
目录下。取值方式:
[UIImage imageNamed:@"PodTest.bundle/imageName"];
//下面这种也可以
NSBundle *bundle = [NSBundle bundleForClass:<#ClassFromPodspec#>];
[UIImage imageWithContentsOfFile:[bundle pathForResource:@"PodTest.bundle/imageName@2x" ofType:@"png"]];
使用s.resources
方式
s.resources = ['PodTest/Assets/*.{png,xib,plist}']
-
Podfile
使用use_frameworks!
编译后资源文件会在test.app/Frameworks/PodName.framework
目录下。取值方式:
NSBundle *bundle = [NSBundle bundleForClass:<#ClassFromPodspec#>];
[UIImage imageWithContentsOfFile:[bundle pathForResource:@"imageName@2x" ofType:@"png"]];
Podfile
不使用use_frameworks!
编译后资源文件会在test.app
目录下。取值方式:
[UIImage imageNamed:@"imageName"];
//下面这种也可以
NSBundle *bundle = [NSBundle bundleForClass:<#ClassFromPodspec#>];
[UIImage imageWithContentsOfFile:[bundle pathForResource:@"imageName@2x" ofType:@"png"]];
注意
注意切换resources
和resource_bundles
测试的时候要把工程clear
一下,不然的话之前的位置那些编译过的资源文件还会存在。
pod install和pod update
源码部分
这里主要讲一下pod install
和pod update
的区别,源码:install.rb和update.rb。
我们先看一下他们主要代码run
部分:
install.rb
def run
verify_podfile_exists!
installer = installer_for_config
installer.repo_update = repo_update?(:default => false)
installer.update = false
installer.install!
end
update.rb
def run
verify_podfile_exists!
installer = installer_for_config
installer.repo_update = repo_update?(:default => true)
if @pods
verify_lockfile_exists!
verify_pods_are_installed!
installer.update = { :pods => @pods }
else
UI.puts 'Update all pods'.yellow
installer.update = true
end
installer.install!
end
源码分析
//这两句install.rb和update.rb都是一样的
verify_podfile_exists! //验证podfile是否存在,不存在抛出异常
installer = installer_for_config //从配置类的实例 config 中获取一个 Installer 的实例置
//是否更新repo索引,就是 ~/.cocoapods/repos/ 下的 repo 是否更新。
//install.rb默认不更新
installer.repo_update = repo_update?(:default => false)
//update.rb默认更新
installer.repo_update = repo_update?(:default => true)
//这段代码的 update.rb 独有的,因为有个命令是更新某个特定的pod用的。pod update podName
if @pods
verify_lockfile_exists! //判断podfile.lock文件是否存在
verify_pods_are_installed! //判断podfile.lock中是否存在这个pod,如果不存在抛出异常
installer.update = { :pods => @pods } //更新这个pod
else
//installer.update表示是否更新pod。
//install.rb默认不更新
installer.update = false
installer.install!
//update.rb默认更新
installer.update = true
installer.install!
结论
-
结论一
根据installer.repo_update
来看,pod update
默认是更新repo
的,而pod install
是不更新的。
所以我们有时候pod update
时会特别慢,就是因为在跟新repo
,特别是CocoaPods
的官方repo。
这时我们就常常会使用pod update --no-repo-update
来禁止更新repo
。
而pod install
是不需要--no-repo-update
,因为它本来就不会更新repo
。
-
结论二
pod update podName
的时候会去Podfile.lock
文件检查这个pod是否安装过,如果没有安装过会抛出异常。
但是如果直接pod update
的话就算Podfile.lock
中没有某个pod,这是不会抛出异常,它会默认帮你先安装好,然后写入到Podfile.lock
文件中。
-
结论三
根据installer.update
来看,pod update
默认是更新pod
的,而pod install
是不更新的。
但是这是相对于pod 'SDWebImage', '~>3.8.0'
这样的写法来用的。比如原来已经安装过3.8.0版本,Podfile.lock
中就为3.8.0版本,满足~>3.8.0
这个条件,那么pod install
的时候是是不会更新到最新版的。
但是pod update
会更新到最新版,同时改写Podfile.lock
中的版本号为最新版。
而pod 'SDWebImage', '3.8.1'
这种写法的话,pod install
和pod update
是一样的。比如Podfile.lock
中原来为3.8.0版本,那么不管怎样都是不等于'3.8.1'
的。
pod install
的时候就会重新安装'3.8.1'
版本,同时改写Podfile.lock
中的版本号为'3.8.1'
。
使用场景
使用场景可以参考官方文档。
主要就是说pod install
用在adding/removing pods
的时候。而当你需要更新pods
的时候使用pod update
。
还有最好把你的Podfile.lock
文件提交到版本管理中,要不然,就会破坏整个逻辑,没有了Podfile.lock
限制你的Pods
中的库的版本。
问题
- ERROR | [iOS] unknown: Encountered an unknown error (Unable to create a source with URL specUrl地址) during validation.
出现这个问题当时是pod没更新。当时pod是1.0.1,pod要更新到1.1.0.rc.2。当时使用的镜像是淘宝的https://ruby.taobao.org/
,而淘宝的少了最新的这个版本,使用ruby china的镜像gem source -a https://gems.ruby-china.org
。
The sandbox is not in sync with the Podfile.lock
每次运行 pod install
命令时创建的 Podfile.lock
文件的副本。如果你遇见过这样的错误 沙盒文件与 Podfile.lock
文件不同步 (The sandbox is not in sync with the Podfile.lock)
,这是因为 Manifest.lock
文件和 Podfile.lock
文件不一致所引起。由于 Pods 所在的目录并不总在版本控制之下,这样可以保证开发者运行 app 之前都能更新他们的 pods,否则 app 可能会 crash,或者在一些不太明显的地方编译失败。
第一次pod setup特别慢
这其实是pod克隆了一份仓库里的文件,这个pod的官方仓库特别大,截止我现在下载的时候有877472个文件,而这个仓库是在github上的,所以国内访问会特别慢。
利用shadowsocks的socks5代理,配置好后明显加速。用下面两条命令配置好后,保持shadowsocks客户端开启就行了。
//shadowsocks的本地端口默认是1080
git config --global http.proxy 'socks5://127.0.0.1:1080'
git config --global https.proxy 'socks5://127.0.0.1:1080'
然后用下面这种直接clone的方式能够看到进度。
cd ~/.cocoapods/repos
git clone https://github.com/CocoaPods/Specs.git
备注:或者使用Proxifier使用全局代理。
fatal: unable to access
问题:
fatal: unable to access 'https://github.com/CocoaPods/Specs.git/': transfer closed with outstanding read data remaining
解决:
Try to cd into ~/.cocoapods/repos/master, then git clean -fd to clean up the working copy, git checkout -- . to ensure you're on master and then git pull manually. This took ages but worked for me.
对应tag的pod库没更新
问题:
有时候我们自己的某个pod库修改了某些代码,但是不想更新tag,就把原来的tag删了加到了最新的代码上。所以说就是tag没更新代码更新了。这时我们在项目上去更新这个pod的时候会发现代码根本没更新。
原因:
首先当你需要安装某个pod库的时候,会在podfile里写上那个库名和对应的版本号,如:
pod 'AFNetworking', '2.2'
然后cocoapods
会去~/.cocoapods/repo/master
路径下进行搜索这个库的podspec
文件(一个tag
对应一个podspec
文件),里面会有这个库的源代码地址,然后根据对应的tag
下载源代码到cocoapods
的缓存目录~/Library/Caches/Cocoapods
。然后再把代码拷贝到你工程的Pods
目录里。
所以当你再次安装这个pod
第三方库的时候,cocopods
会去缓存目录里查看有没有对应tag
的源码,如果有的话就直接使用这个缓存的源码。当然如果你对工程的Pods
有这个源码的话就直接使用了,也不会更新。
解决:
删除缓存目录
~/Library/Caches/Cocoapods
里对应的源码。
删除工程目录里的Podfile.lock
文件。删除工程Pods
目录下对应的源码。
私有podspec上传出错
- s.subspec互相之间的依赖问题
当你把源码分为多个s.subspec
的时候,其实是需要在对应的subspec
下面做依赖的,如下面这样:
s.subspec 'ViewController' do |ss|
ss.source_files = 'PodName/ViewController/*.{h,m}'
end
s.subspec 'View' do |ss|
ss.source_files = 'PodName/View/*.{h,m}'
end
如上面这样写的话(ViewController中有文件依赖于View),你在本地编译安装pod库其实是不会报错的。但是如果你要pod spec lint
的验证这个podspec
,它会报错,提示'ViewController'中某个文件找不到引用View
中的文件。
其实pod在做检查的时候会把ViewController
和View
当做两个独立的spec,如果ViewController
中有使用View
中的文件的话,需要添加依赖关系,如下这样就是正确的。
s.subspec 'ViewController' do |ss|
ss.source_files = 'PodName/ViewController/*.{h,m}'
ss.dependency 'PodName/View'
end
s.subspec 'View' do |ss|
ss.source_files = 'PodName/View/*.{h,m}'
end
参考
给 Pod 添加资源文件
pod install和pod update背后那点事
CocoaPods 都做了什么?
Git Howto: Mirror a GitHub repo without pull refs