工具相关拓展层iOS进阶

cocoapods进阶

2016-11-28  本文已影响2879人  齐滇大圣

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文件
使用Cocoapods创建私有podspec
比如:pod spec create https://github.com/walkdianzi/DSCategories

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仓库还是根据你需要的podgithub上下载源码,都会发现特别慢。github在国内没有服务器,虽然国内可以访问,但是速度慢的可怜。我发现即使翻墙了速度也不算快。

所以我就想把cocoapods的官方仓库,和一些比较著名的pod库做一个镜像。但是严格的说也不能算是镜像,比如我cocoapods的spec仓库就没有整个镜像过来,我只是把自己需要podspec文件移到了一个自己的spec仓库里,毕竟官方的spec仓库里文件已经快多达百万个了,就算你在国内服务器下,也需要一些时间。

还有就是我自己没有架服务器来做一个定时更新的事。我现在是把spec仓库和一些自己项目中需要的第三方仓库放在coding上面,然后用自己的电脑定时做一下和github上的源码同步。所以非工作日或者节假日我没开电脑的情况下就不会更新了,如果有需要可以来私信我进行更新。

小型podspec仓库地址:smallSpecs

spec仓库镜像

以上遍历拷贝替换部分都是使用脚本完成的:脚本,如果要使用这个脚本需要修改里面的部分绝对路径。

pod第三方库镜像

这里也是现在一个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-refsFETCH_HEAD文件中读取需要同步的headstagspull requst的(下图就是packed-refs文件的截图),但是我们只需要headstags,是不需要pull requst的,在同步pull requst的时候就会出现以上错误。具体可以参考Git Howto: Mirror a GitHub repo without pull refs

packed-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动态库。下面就是swiftpod使用--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.resourcess.resource_bundles

use_frameworks!

我们在Podfile文件中是否加入use_frameworks!,我们资源文件保存的位置也会有所不同。

我们使用这段代码[NSBundle bundleForClass:<#ClassFromPodspec#>]能够得到类对应所在的bundle对象。

  • 如果我们使用了use_frameworks!,那么 pod 将以 framework 形式被链接,上面那段代码返回的bundle对象是frameworkbundle,其实就是.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 targetmainBundle,其实就是.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.resourcess.resource_bundles的区别主要是后者在上面代码[NSBundle bundleForClass:<#ClassFromPodspec#>]中得到的那个bundle对象下面再生成一个.bundle资源文件夹,相较于s.resources直接平铺的方式更好的组织了文件,避免资源文件命名冲突。

举个例子比如我有一个pod叫做PodTest,使用s.resource_bundles方式

s.resource_bundles = {
'PodTest'(.bundle文件名) => 'PodTest/Assets/*.{png,xib,plist}'
}

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}']

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"]];

注意

注意切换resourcesresource_bundles测试的时候要把工程clear一下,不然的话之前的位置那些编译过的资源文件还会存在。


pod install和pod update

源码部分

这里主要讲一下pod installpod update的区别,源码:install.rbupdate.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!

结论

所以我们有时候pod update时会特别慢,就是因为在跟新repo,特别是CocoaPods的官方repo

这时我们就常常会使用pod update --no-repo-update来禁止更新repo

pod install是不需要--no-repo-update,因为它本来就不会更新repo

但是如果直接pod update的话就算Podfile.lock中没有某个pod,这是不会抛出异常,它会默认帮你先安装好,然后写入到Podfile.lock文件中。

但是这是相对于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 installpod 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的时候,其实是需要在对应的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在做检查的时候会把ViewControllerView当做两个独立的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

上一篇下一篇

猜你喜欢

热点阅读