CocoaPods使用总结

使用CocoaPods也有很长一段时间了,最近几个月的时间里也主导了公司私有Pods的创建和使用。在此期间踩过了不少坑,在踩坑的过程中也收获了不少经验,更加熟练地掌握了CocoaPods的一些指令的使用。本篇作为这段时间收获的备忘。

一、CocoaPods简介

CocoaPods是专门为iOS工程提供第三方依赖库的管理工具,通过CocoaPods,我们可以更方便地管理每个第三方库的版本,而且不需要我们做太多的配置,就可以直观、集中和自动化地管理我们项目的第三方库。

CocoaPods将所有依赖的库都放在一个名为Pods的项目下,然后让主项目依赖Pods项目。然后,我们编码工作都从主项目转移到Pods项目。Pods项目最终会编译为一个libPod-项目名.a静态库,主项目依赖于这个静态库。

对于资源文件,CocoaPods 提供了一个名为 Pods-resources.sh 的 bash 脚本,该脚本在每次项目编译的时候都会执行,将第三方库的各种资源文件复制到目标目录中。

CocoaPods 通过一个名为 Pods.xcconfig 的文件来在编译时设置所有的依赖和参数。

CocoaPods是用 Ruby 写的,并由若干个 Ruby 包 (gems) 构成的。在解析整合过程中,最重要的几个 gems 分别是: CocoaPods/CocoaPods, CocoaPods/Core, 和 CocoaPods/Xcodeproj

CocoaPod的核心组件

  • CocoaPods/CocoaPod
    这是一个面向用户的组件,每当执行一个 pod 命令时,这个组件都将被激活。该组件包括了所有使用 CocoaPods 涉及到的功能,并且还能通过调用所有其它的 gems 来执行任务。

  • CocoaPods/Core
    Core 组件提供支持与 CocoaPods 相关文件的处理,文件主要是 Podfile 和 podspecs。

  • Podfile
    Podfile 是一个文件,用于定义项目所需要使用的第三方库。该文件支持高度定制,你可以根据个人喜好对其做出定制。更多相关信息,请查阅 Podfile 指南。

  • Podspec
    .podspec 也是一个文件,该文件描述了一个库是怎样被添加到工程中的。它支持的功能有:列出源文件、framework、编译选项和某个库所需要的依赖等。

  • CocoaPods/Xcodeproj
    这个 gem 组件负责所有工程文件的整合。它能够创建并修改 .xcodeproj 和 .xcworkspace 文件。它也可以作为单独的一个 gem 包使用。如果你想要写一个脚本来方便地修改工程文件,那么可以使用这个 gem。

CocoaPods的安装和配置,以及Podfile中第三方库引用的语法规则(特别是版本号的语法格式)这里就不赘述了,下面挑重点讲一讲。

二、多target时Podfile该如何写?

我的建议是使用Ruby语法,定义不同的分组,然后不同的target可以自由选择依赖哪些分组,这种方式看起来更简洁,对于多target的项目来说也更友好:

platform :ios, '8.0'

def commonPods #通用pods集
    pod 'AFNetworking', '~> 2.0'
    pod 'Masonry'
end

def appOnlyPods #app专用pods集
    pod 'MBProgressHUD'
end

def extensionPods #扩展专用pods集
    pod 'GTSDKExtension'
end

target :TestCocoaPods do
    commonPods
    appOnlyPods

    target :TestCocoaPodsTests do
    inherit! :search_paths
    # Pods for testing
    end

    target :TestCocoaPodsUITests do
        inherit! :search_paths
        # Pods for testing
    end
end

target :SecondTarget do
    commonPods
end

三、如何忽略Pods警告?

有些第三方Pod集成进来会有一大堆警告信息,如果你看着比较难受想把它忽略的话,在Podfile中对应的target或分组下加上关键字inhibit_all_warnings即可。

四、如何直接引用第三方库中的头文件?

在用CocoaPods集成第三方库之后,默认情况下,我们需要使用类似#import <XXX/YYY.h>的方式引入第三方库的头文件。我们可以在Build Settings -> User Header Search Paths中添加${SRCROOT}并设置成recursive,这样我们就可以直接使用#impot "YYY.h"这种方式了。

五、pod install or pod update?

如官方文档所说,pod installpod update确实是大家最容易搞混的两条指令,很多人还没搞清楚这两条指令的区别,反正不管三七二十一上来就是一个pod update,大家一定要搞清楚这两条指令的区别。

按照官方文档所说,pod install在第一次检索集成第三方以及每一次在Podfile中新增、更改或删除pod的时候使用。每一次执行pod install命令,它都会下载安装新的pod,并且会把每一个安装的pod的版本信息写入Podfile.lock文件。Podfile.lock文件跟踪每一个安装的pod的版本并且上锁。每一次执行pod install命令,只解决还没有在Podfile.lock中列出的依赖:对于已在Podfile.lock中列出的pod,会下载指定的版本,不会检查是否有新版本。对于没有在Podfile.lock中列出的pod,它会搜索并安装Podfile中指定的版本。

直接执行pod update命令会检查安装Podfile中列出的所有pod的新版本(往往比较慢)。

执行pod update PODNAME命令会检查PODNAME的新版本(不考虑Podfile.lock中记录的版本信息),它会把PODNAME更新为最新版本,只要跟Podfile中指定的版本匹配。也就是说,pod update PODNAME将PODNAME更新到Podfile中指定的版本,可以是更新到老版本也可以是更新到新版本,取决于Podfile。(比如:如果此时Podfile中指定了pod 'AFNetworking', '~> 2.0',此时执行pod update AFNetworking并不会把AFNetworking更新到最新版本(因为此时的版本满足大于等于2.0版),必须先修改Podfile中的版本信息才会更新到指定版本)。

两者的区别:

  • pod install命令来安装新的pod,每次在Podfile中新增和删除pod都使用pod install命令。

  • 在Podfile中添加新的pod后应该用pod install命令,而不是pod update命令。通过pod install命令安装新的pod而不用担心在同一进程中修改已有的pod。

  • pod update命令仅用在更新指定pod到指定版本或者更新所有pod。

我的建议是:该用pod install的时候不要用pod update PODNAME。另外,尽量不要用pod update,因为它是全部检查一遍,不仅慢有时候还会出现坑。比如有一个依赖的第三方库本来是2.0版本的用的好好的,因为它是国外的资源,下载起来非常慢,我们在没有bug的情况下是不希望轻易去更新它的,那么如果你上来就是一个pod update指令,OK, 如果你Podfile中指定了每次使用最新版本(不指定版本号),那么CocoaPods就会去下载最新的这个第三方库,那在下载完成之前你还要不要做其他事情了?这还是情况好的,如果这个最新的版本一直下载失败,所以一直集成失败怎么办?

六、如何创建私有Pod?

要创建私有Pod,首先我们需要两个私有仓库,一个放私有Pod源码,一个放私有Pod的说明书(类似公有Pod的CocoaPods/Specs)。

1、添加私有Spec仓库到本地

pod repo add privateSpecs your_privateSpecs.git

如果执行成功,之后便可以通过pod repo list命令查看本地Spec仓库列表,正常情况下会有一个公有的CocoaPods官方的master repo 和你的 privateSpecs repo,并可以看到它们在本地的存放路径(其实在~/.cocoapods/repos目录下)。

2、创建私有Pod

在私有Pod代码所在文件夹下执行pod spec create your_podName在该目录下创建一个your_podName.podspec说明书文件。之后的工作就是编辑这个说明书文件了,这里简单注明一下规则:

Pod::Spec.new do |s|

  s.name         = "ATCategory"
  s.version      = "0.0.1"
  s.summary      = "共用扩展类集合"
  s.description  = <<-DESC
  大家如果需要用到扩展,都使用这里已有的扩展啦。
                   DESC
  s.homepage     = "your_privatePodGit_address/ATCategory"
  s.author       = { "ApesTalk" => "https://github.com/apestalk" }
  s.platform     = :ios
  s.platform     = :ios, "8.0"
  s.source       = { :git => "your_privatePodGit_address", :tag => "#{s.version}"
  # 如果你有多个私有Pod放在一个仓库里,你可以修改tag像下面这样,对应打tag的时候的规则就对应需要变成PodName-v0.0.1这样子了
  # s.source       = { :git => "your_privatePodGit_address", :tag => s.name + "-v"+"#{s.version}"
}
  s.source_files  = 'ATCategory/**/*'
  s.public_header_files = 'ATCategory/Category/*.h'
  s.requires_arc = true
  s.frameworks = 'UIKit','Foundation'

# 依赖的系统library,这里是指系统的类似libz.tbd、libxml2.tbd这类的系统库
# s.library = 'z' // 单个
# s.libraries = 'z','xml2' // 多个

# 第三方.a
# s.vendored_libraries =
# 第三方frameworks文件
# s.vendored_frameworks =
# 依赖关系,该项目所依赖的其他库,如果有多个需要填写多个s.dependency
# s.dependency 'AFNetworking', '~> 2.3'
# 资源文件地址
# s.resource_bundles = {
#   'ATCategory' => ['ATCategory/Images/*.png']
# }
end

3、提交源代码并打tag

注意这里tag必须跟podspec文件中的tag保持一致,因为CocoaPods是通过podspec文件中的tag去找源文件的,如果tag对应不起来就会验证失败。打好tag提交到远端。

4、验证podspec文件合法性和可选参数

有两种验证方式,一种是本地验证pod lib lint your_podName.podspec和联网验证pod spec lint your_podName.podspec。建议大家都用联网验证。

这里可选参数有:

  • --allow-warnings:允许警告
  • --sources=‘master,privateSpecs:指定源,比如你的私有pod同时依赖了公有库和私有库,你必须指定源才行,因为默认只会去在公有源中查找对应的依赖
  • --use-libraries:如果使用了静态库,记得加上它

5、提交说明书文件到私有说明书库

pod repo push privateSpecs your_podName.podspec,同样的加上上面验证时使用到的可选参数。

6、如何使用素材?

官方建议pod中的素材用bundle的形式避免和主项目中的文件名发生冲突,那么集成后我们如何使用bundle中的素材呢?

NSBundle *bundle = [NSBundle mainBundle];
//NSBundle *bundle = [NSBundle bundleForClass:[ClassFromPodspec class]];//对于静态库,拿到的是mainBundle,如果是动态库,拿到的是类所在的bundle。使用动态库需要在Podfile中开启use_frameworks!
NSURL *wttpodBundleURL = [bundle URLForResource:@"WTTPod" withExtension:@"bundle"];
NSBundle *wttpodBundle = [NSBundle bundleWithURL: wttpodBundleURL];
UIImage *img = [UIImage imageNamed:@"Chat_checkin_empty_stu" inBundle:wttpodBundle compatibleWithTraitCollection:nil];

7、关于subspec

如果我们的pod中文件比较多,而我们又希望能像AFNetworking那样集成后分几个物理文件夹(默认会把所有文件都放在一个物理文件夹下,文件太多会显得很乱),那么就要用到subspec来把我们的pod分成几个独立的子模块。


s.subspec '子模块名称' do |别名,不能和子模块名称相同,比如ss|

ss.source_files = ''

end

具体怎么用,大家可以参考AFNetworking.podspec文件中的写法。

七、如何使用私有库

如果我们同时使用了公有库和私有库,我们只需要在Podfile的头部同时把公有库和私有库的source加上即可。

八、pod search搜不到私有库?或者搜得到pod install失败?

在提交私有库说明书之后,先执行一下pod repo update privateSpecs,然后再集成。

九、集成某一个pod速度过慢,比如MobileVLCKit总是下载失败

把对应版本的MobileVLCKit下载下来放在访问速度更快的地方(比如内网服务器或者本机用Python开启一个FTP服务)在本机master repo源中搜索找到对应版本的MobileVLCKit.podspec.json文件,把其中的source改成我们存放MobileVLCKit.tar.xz文件地址,之后再执行相关指令集成。

如何利用Python开启一个本地FTP服务:

cd到要共享的目录下,执行python -m SimpleHTTPServer 8000,之后同一个局域网内就可以通过本机ip:8000访问到该共享文件夹了。


"http":"http://192.168.210.111:8000/MobileVLCKit-3.1.2-bf58e19-37855b857a.tar.xz"

获取本机IP地址的方法:按住Option的同时点下Mac菜单栏的无线网Icon,在下拉列表中即可看到IP地址。也可以在终端中输入ifconfig en0命令查看。

十、验证podspec时,报错 symbol(s) not found for architecture i386

检查一下是否私有Pod中使用到的什么文件不支持i386架构,比如什么.a文件。

解决办法:在podspec文件中指定支持的架构

valid_archs = ['armv7s','arm64',]
s.xcconfig = {
  'VALID_ARCHS' =>  valid_archs.join(' '),
}
s.pod_target_xcconfig = {
    'ARCHS[sdk=iphonesimulator*]' => '$(ARCHS_STANDARD_64_BIT)'
}

同样的,如果在验证过程中遇到xcodebuild: Returned an unsuccessful exit code.却不报具体的错,去看看NOTE类型的信息,如果看到missing required architecture i386 in file此类消息,说明某个.a或者frawork不支持i386架构,需要在podspec文件中写明该pod支持哪些架构。

十一、pod init失败?

用Xcode9.4.1新建一个项目,然后执行pod init(Cocoapods1.4.0版本)时提示失败,错误提示如下:


――― MARKDOWN TEMPLATE ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――

### Command


/usr/local/Cellar/cocoapods/1.4.0/libexec/bin/pod init


### Report

* What did you do?

* What did you expect to happen?

* What happened instead?


### Stack


   CocoaPods : 1.4.0
        Ruby : ruby 2.3.3p222 (2016-11-21 revision 56859) [universal.x86_64-darwin17]
    RubyGems : 2.5.2
        Host : Mac OS X 10.13.3 (17D47)
       Xcode : 9.4.1 (9F2000)
         Git : git version 2.15.2 (Apple Git-101.1)
Ruby lib dir : /System/Library/Frameworks/Ruby.framework/Versions/2.3/usr/lib



### Plugins


cocoapods-deintegrate : 1.0.2
cocoapods-plugins     : 1.0.0
cocoapods-search      : 1.0.0
cocoapods-stats       : 1.0.0
cocoapods-trunk       : 1.3.0
cocoapods-try         : 1.1.0


### Error


RuntimeError - [Xcodeproj] Unknown object version.
/usr/local/Cellar/cocoapods/1.4.0/libexec/gems/xcodeproj-1.5.4/lib/xcodeproj/project.rb:217:in `initialize_from_file'
/usr/local/Cellar/cocoapods/1.4.0/libexec/gems/xcodeproj-1.5.4/lib/xcodeproj/project.rb:102:in `open'
/usr/local/Cellar/cocoapods/1.4.0/libexec/gems/cocoapods-1.4.0/lib/cocoapods/command/init.rb:41:in `validate!'
/Library/Ruby/Gems/2.3.0/gems/claide-1.0.2/lib/claide/command.rb:333:in `run'
/usr/local/Cellar/cocoapods/1.4.0/libexec/gems/cocoapods-1.4.0/lib/cocoapods/command.rb:52:in `run'
/usr/local/Cellar/cocoapods/1.4.0/libexec/gems/cocoapods-1.4.0/bin/pod:55:in `<top (required)>'
/usr/local/Cellar/cocoapods/1.4.0/libexec/bin/pod:22:in `load'
/usr/local/Cellar/cocoapods/1.4.0/libexec/bin/pod:22:in `<main>'


――― TEMPLATE END ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――

[!] Oh no, an error occurred.

Search for existing GitHub issues similar to yours:
https://github.com/CocoaPods/CocoaPods/search?q=%5BXcodeproj%5D+Unknown+object+version.&type=Issues

If none exists, create a ticket, with the template displayed above, on:
https://github.com/CocoaPods/CocoaPods/issues/new

Be sure to first read the contributing guide for details on how to properly submit a ticket:
https://github.com/CocoaPods/CocoaPods/blob/master/CONTRIBUTING.md

Don't forget to anonymize any private data!

Looking for related issues on cocoapods/cocoapods...
 - Pod Update: RuntimeError - [Xcodeproj] Unknown object version. Xcode Beta 5
   https://github.com/CocoaPods/CocoaPods/issues/8003 [closed] [17 comments]
   a day ago

 - RuntimeError - [Xcodeproj] Unknown object version.
   https://github.com/CocoaPods/CocoaPods/issues/7697 [closed] [28 comments]
   3 weeks ago

 - Pod init. Unknown object version
   https://github.com/CocoaPods/CocoaPods/issues/7907 [closed] [2 comments]
   03 Jul 2018

and 42 more at:
https://github.com/cocoapods/cocoapods/search?q=[Xcodeproj]%20Unknown%20object%20version.&type=Issues&utf8=✓

这种失败原因是Cocoapods和xcodeproj版本兼容问题。

尝试了网上的解决办法Run gem install xcodeproj:1.4.1,依然失败。

解决办法:打开项目,在Project Document下将Project Format从Xcode 9.3-compatible修改为Xcode 8.0-compatible即可。

Xocde_project_format

十二、在pod中引入项目文件报错(file not found)

在开发中有时候需要在pod中import项目中的文件进行调试或测试(当然这种情况比较少见),但是当你输入import的时候会发现系统根本没法联想到你想用的项目中的文件,即使你手动写入,也会报file not found的错误。

此时,可以在我们的Pods项目中的Build settings下找到 User Header Search Paths,添加一行,并设置$(SRCROOT)/..为recursive。注意有/..,意思是在上级目录下递归查找文件。

十三、清除本地缓存(version重用)

如果在某个tag下(假设WTTestPodCache-v0.0.1)验证私有pod失败,修改代码后,我们还希望使用这个版本号而不是去修改podspec文件中的version字段。我们就需要清除Pod的本地缓存了,否则会因为该tag对应的缓存问题,还是一样验证不过。这时候我们需要先把代码修改正确后提交,然后把本地和远程的原tag删除,最后在最新代码节点上打一个相同的tag提交再次提交验证。---参考:CocoaPods清理本地缓存

我们在验证的时候加上--verbose参数,会看到CocoaPods有如下拷贝操作:

copyfromto.png

copying XXX from 后面跟着的就是本地缓存的地址,进入该路径下会看到WTTestPodCache,我们删除本地和远程tag后,本地该缓存依然存在,当执行pod cache clean WTTestPodCache后,本地该缓存就不存在了,在验证私有Pod时CocoaPods就会尝试再次从远程拉取。

  • 查看本地缓存列表 pod cache list

  • 清除本地缓存 pod cache clean XXX

十四、集成MobileVLCKit时报错Lzma library error

如果在验证pod,安装某个库时报Lzma库的错误:

ERROR | [iOS] unknown: Encountered an unknown error ([!] /usr/bin/tar xf /var/folders/tf/jlbz0pq91m16571fgc7ywbk80000gn/T/d20191016-38557-1l50cyi/file.txz -C /var/folders/tf/jlbz0pq91m16571fgc7ywbk80000gn/T/d20191016-38557-1l50cyi

MobileVLCKit-binary/MobileVLCKit.framework/MobileVLCKit: Lzma library error:  No progress is possible
tar: Error exit delayed from previous errors.

这属于pod依赖的这个库的压缩包解压失败了,Lzma library是用来解压用的,如果压缩包损坏了,会导致解压失败,从而报这个错误,此时需要检查压缩包是否正常。

十五、本地多个Ruby环境导致pod执行失败

如果执行任意pod指令都失败,失败信息像下面这样:

Ignoring ffi-1.13.1 because its extensions are not built. try: gem pristine ffi —version 1.13.1
Ignoring gem-wrappers-1.4.0 because its extensions are not builts. Try: gem pristine gem-wrappers —version 1.4.0
Traceback (most recent call last):
    23: from /USR/LOCAL/BIN/pod:23:in `<main>`
    22: from /USR/LOCAL/BIN/pod:23:in `load`
    21: from /Users/username/.rvm/rubies/ruby-2.6.0/lib/ruby/gems/2.6.0/gems/cocoapods-1.9.3/bin/pod:36:in `<top (required)>`
    20: from /System/Library/Frameworks/Ruby.frameworks/Versions/2.6/usr/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require`

如果是这样子的话,根据错误提示说明是本地有多个Ruby环境导致的,应该是中途什么时候看了一些教程安装了rvm然后又装了个Ruby,删除rvm目录下的Ruby即可。

十六、慎用--skip-import-validation与--skip-tests

如果pod怎么都验证不过,始终只有这么一条提示([iOS] xcodebuild: Returned an unsuccessful exit code),没有任何其他ERROR信息,可以加上这两个选项了让pod验证通过。
--skip-tests: 在验证期间跳过构建和运行测试
--skip-import-validation: 跳过验证pod是否可以导入

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

推荐阅读更多精彩内容