iOS Cocoapods开发完整配置详细分析

2021年3月8日16:34:11 发表评论 1,501 次浏览

本文概述

前言

iOS Cocoapods开发完整配置

CocoaPods是一个Xcode库依赖管理工具,我们可以使用一个Podfile文件指定项目的所有依赖,CocoaPods会解决库之间的依赖关系,获取生成的源代码,然后将其链接到Xcode工作空间中来构建你的项目。

CocoaPods的最终的目标是通过创建一个更加集中化的生态系统来提高第三方开源库的可发现性和参与度。

因为最近准备开发iOS应用,所以打算重新复习一下CocoaPods,并不会展开系列进行讨论,打算用一篇文章讨论完CocoaPods的重点内容。对比Android的Gradle、Java的Maven、C/C++的cmake,CocoaPods似乎没有过多的项目构建的相关配置,像CocoaPods官方说的,它着重于库依赖管理,那么也不算太复杂或太困难。

既然是依赖管理,那么我们可以设想它应该有的功能包括:

  • 指定依赖库的名称和版本。
  • 排除依赖。
  • 安装、更新和删除依赖。

Cocoapods的依赖配置文件是Podfile,没有后缀名。下面我们从CocoaPods的安装开始进行详细介绍。

CocoaPods安装

CocoaPods是用Ruby构建的,可以使用macOS上的默认Ruby进行安装。你可以使用Ruby版本管理器(RVM),但是我们建议你使用macOS上可用的标准Ruby,除非你知道自己在做什么。

MacOS默认已经有Ruby的包管理器Rubygems了,由于国内的特殊环境,最好在安装Cocoapods之前先检查和更改gem的源:

$ gem sources --remove https://rubygems.org/
$ gem source -a https://gems.ruby-china.com/
$ gem source -l
*** CURRENT SOURCES ***

接着就可以开始安装了,安装CocoaPods命令如下(更新CocoaPods命令也是这个):

$ sudo gem install cocoapods

在安装CocoaPods之前,需要确保你已经安装了Xcode,否则会安装不成功。或者是更新预发布版本:

$ [sudo] gem install cocoapods --pre

如果有新版本,CocoaPods会通知你。

CocoaPods的pod install和pod update的区别

pod install并非只在第一次运行,它们的使用场景如下:

  • pod install:当你的项目添加、更改或删除库时候,应该使用pod install命令。
  • pod update [podName]:只有在你更新某一个库的时候才使用。

pod install会下载和安装新的库,每个库的版本会使用Podfile.lock文件进行版本锁定。当你运行pod install的时候,它只会检查不包含在Podfile.lock中的库。对于已经存在Podfile.lock中的库,它会使用其中指定的特定版本,并进行下载,忽略更新。对于不存在Podfile.lock中的库,它会尝试解析和搜索Podfile中的库。

pod outdated命令会列出Podfile.lock中的所有库的最新版本,如果你随后使用pod update [podName]进行更新(只要符合配置文件的库版本限制),那么该库就有可能被更新。

使用pod update podName会尽可能更新指定的库(只要符合配置文件的库版本限制),同时忽略Podfile.lock中的版本锁定。如果只是运行pod update,那么Cocoapods会尽可能更新所有的库。

当你增加一个库到Podfile中的时候,你应该使用pod install而不是pod update,只有当你想要更新某个特定库(或所有库)的版本时,才会使用pod update。个人觉得相对指定跳跃的版本,指定库的特定版本会安全一点(但是Cocoapods不能保证当前使用库A的依赖A1的版本也被锁定了,所以尽可能使用最新版,并且如果库更新了,尽可能跟着更新……)。

当使用git等版本管理工具时,应该尽可能提交Podfile和Podfile.lock文件。

如何在Xcode中使用CocoaPods?

在使用CocoaPods之前,你需要检查CocoaPods的Specs公共规范(https://github.com/CocoaPods/Specs)或cocoapods.org是否可用,这个这个Specs存储库包含公共CocoaPods规范。

Xcode项目初始化

首先创建一个Xcode项目,创建完成后关闭Xcode。

然后在新项目的根目录创建一个Podfile,并添加相应的依赖:

target 'iOSApp' do
  pod 'AFNetworking', '~> 3.0'
  pod 'FBSDKCoreKit', '~> 4.9'
end

创建Podfile也可以直接使用命令:$ pod init来完成。

接着在项目根目录中运行命令:$ pod install安装新的库。

最后打开App.xcworkspace(你应该总是使用该文件打开项目),并进行项目构建。

与现有的工作空间集成

将CocoaPods与现有的工作空间集成需要在Podfile中增加一行代码。只需在你的目标块之外指定.xcworkspace文件名,如下所示:

workspace 'MyWorkspace'

什么是Podfile.lock?

此文件是在第一次运行pod install后生成的,并跟踪每个已安装pod的版本。例如,设想在Podfile中指定了以下依赖项:

pod 'RestKit'

运行pod install将安装RestKit的当前版本,从而产生一个Podfile.lock,它指示所安装的确切版本(例如RestKit 0.10.3)。多亏了Podfile。锁定后,在另一台机器上运行pod install仍然会安装RestKit 0.10.3,即使有新的版本可用。CocoaPods尊重Podfile.lock中记录的Pod版本,除非依赖项在Podfile中被更新,或者pod install被调用(这将导致一个新的Podfile.lock被生成),否则将被锁定。通过这种方式,CocoaPods避免了因依赖项的意外变化而引起的头痛。

CocoaPods做了什么工作?

CocoaPods在Xcode中,直接引用ruby源代码,它:

  • 创建或更新工作区。
  • 如果需要,将项目添加到工作区。
  • 如果需要,将CocoaPods静态库项目添加到工作区。
  • 添加libPods.a 到: targets =>构建阶段=>链接库。
  • 将CocoaPods的Xcode配置文件添加到应用程序的项目中。
  • 根据CocoaPods改变你的应用的目标配置。
  • 添加一个构建阶段,从你安装的任何pod复制资源到你的应用包。即,在所有其他构建阶段之后的“脚本构建阶段”,包括以下内容:
    • Shell:/bin/sh
    • Script:$ {SRCROOT} /Pods/ PodsResources.sh

Podfile文件配置解析

Podfile是什么?

Podfile是一个描述一个或多个Xcode项目目标之间依赖关系的规范。该文件应该简单地命名为Podfile,指南中的所有示例都基于CocoaPods 1.0及以上版本。

以下的例子添加一个库Alamofire到一个特定target MyApp中:

target 'MyApp' do
  use_frameworks!
  pod 'Alamofire', '~> 3.0'
end

下面是一个更为复杂的Podfile:

source 'https://github.com/CocoaPods/Specs.git'
source 'https://github.com/Artsy/Specs.git'

platform :ios, '9.0'
inhibit_all_warnings!

target 'MyApp' do
  pod 'GoogleAnalytics', '~> 3.1'

  # 当前目标有自己的OCMock副本,并可以通过托管测试目标的应用程序访问GoogleAnalytics
  target 'MyAppTests' do
    inherit! :search_paths
    pod 'OCMock', '~> 2.0.1'
  end
end

post_install do |installer|
  installer.pods_project.targets.each do |target|
    puts target.name
  end
end

如果希望多个目标共享相同的pod,请使用abstract_target:

# 在任何Xcode项目中都没有被称为"Shows"的目标
abstract_target 'Shows' do
  pod 'ShowsKit'
  pod 'Fabric'

  # 有自己的ShowsKit + ShowWebAuth
  target 'ShowsiOS' do
    pod 'ShowWebAuth'
  end

  # 有自己的ShowsKit + ShowTVAuth
  target 'ShowsTV' do
    pod 'ShowTVAuth'
  end
end

在Podfile的根有一个隐式的抽象target,所以你可以这样写上面的例子:

pod 'ShowsKit'
pod 'Fabric'

# 有自己的ShowsKit + ShowWebAuth
target 'ShowsiOS' do
  pod 'ShowWebAuth'
end

# 有自己的ShowsKit + ShowTVAuth
target 'ShowsTV' do
  pod 'ShowTVAuth'
end

指定Pod版本

如果你只是想使用一个Pod或库的最新版,那么你可以简单地作如下声明(省略版本声明):

pod 'SSZipArchive'

在项目的后面,你可能希望使用Pod的特定版本,在这种情况下,你可以指定那个版本号。

pod 'Objection', '0.9'

除了无版本或特定版本之外,还可以使用逻辑运算符:

  • '> 0.1' 任意大于0.1的版本。
  • '>= 0.1' 大于或等于0.1的版本。
  • '< 0.1' 小于0.1的版本。
  • '<= 0.1' 小于或等于0.1的版本。

除了逻辑运算符CocoaPods还有一个乐观的运算符~>(版本范围指定):

  • '~> 0.1.2' :0.1.2到0.2的版本,不包括0.2或更高的版本。
  • '~> 0.1' :0.1到1.0的版本,不包括0.1或更高的版本。
  • '~> 0' :0到1.0的版本,不包括1.0或更高的版本。

使用计算机本地文件夹中的文件

如果你想与客户端项目一起开发一个Pod,你可以使用:path。

pod 'Alamofire', :path => '~/Documents/Alamofire'

使用这个选项CocoaPods将假定给定的文件夹是Pod的根,并将从那里直接链接Pods项目中的文件。这意味着你的编辑将在CocoaPods安装之间持续。引用的文件夹可以是你最喜欢的SCM的签出,甚至是当前repo的git子模块。

注意,Pod文件的podspec应该在指定的文件夹中。

来自库存储库根中的podspec

有时你可能想要使用Pod的尖端版本,一个特定版本或你自己的叉子。如果是这种情况,你可以用pod声明指定它。

使用储存库的master分支:

pod 'Alamofire', :git => 'https://github.com/Alamofire/Alamofire.git'

使用储存库中的不同分支:

pod 'Alamofire', :git => 'https://github.com/Alamofire/Alamofire.git', :branch => 'dev'

使用储存库的标签:

pod 'Alamofire', :git => 'https://github.com/Alamofire/Alamofire.git', :tag => '3.1.1'

或者使用某一个提交:

pod 'Alamofire', :git => 'https://github.com/Alamofire/Alamofire.git', :commit => '0f506b1c45'

但是,需要注意的是,这意味着该版本必须满足其他Pod对该Pod的任何其他依赖。

podspec文件应该在repo的根目录中,如果这个库的repo中还没有podspec文件,你将不得不使用下面几节中概述的方法之一。

CocoaPods插件

CocoaPods是一个社区项目,由少数维护者运营,需要维护大量的表面积。可以肯定地说,CocoaPods不可能支持Xcode支持的所有功能,即便如此,该团队也不得不对许多潜在有用的功能说“不”。

并没有就此打住,早在2013年,CocoaPods就增加了对CocoaPods插件的支持。这个插件架构允许其他人扩展CocoaPods来支持那些不符合依赖管理和生态系统增长主要目标的特性。

CocoaPods插件可以做什么?

CocoaPods插件可以:

  • 在安装过程之前和之后都要挂钩到安装过程
  • 向pod添加新命令
  • 做他们想做的事情,因为Ruby是一种非常动态的语言

这意味着插件的范围通常与向构建过程添加特性有关,但实际上可以做任何你想做的事情。例如cocoapods-roulette会生成一个带有三个随机pod的新iOS应用。我们保留了一个所有插件的相对列表,您可以在本文的最后看到它们。

我如何安装一个插件?

如果你以前从未使用过Gemfile,或者想要复习一下,你会想要使用Gemfile——请查看我们的指南“使用Gemfile”。所有CocoaPods插件都是gem,首先要将它们添加到Gemfile中,然后需要说明它们存在于Podfile中。

例如,要使用cocoapods-repo-update -你需要修改你的Gemfile:

  source 'https://rubygems.org'

  gem 'cocoapods'
+ gem 'cocoapods-repo-update'
  gem 'fastlane'

然后在你的Podfile中添加对它的引用:

platform :ios, '9.0'
+ plugin 'cocoapods-repo-update'

  use_frameworks!

  # OWS Pods
  pod 'SignalCoreKit', git: 'https://github.com/signalapp/SignalCoreKit.git', testspecs: ["Tests"]

运行bundle exec pod install时,cocopods -repo-update插件也会被执行。

一个CocoaPods项目的Podfile示例

下面是来自github项目的Podfile配置示例,这个示例不算复杂。

  source 'https://github.com/artsy/Specs.git'
  source 'https://cdn.cocoapods.org/'
  
  plugin 'cocoapods-keys', {
    :project => 'Eidolon',
    :keys => [
      'ArtsyAPIClientSecret',
      'ArtsyAPIClientKey',
      'HockeyProductionSecret',
      'HockeyBetaSecret',
      'SegmentWriteKey',
      'CardflightProductionAPIClientKey',
      'CardflightProductionMerchantAccountToken',
      'StripeProductionPublishableKey',
      'CardflightStagingAPIClientKey',
      'CardflightStagingMerchantAccountToken',
      'StripeStagingPublishableKey'
    ]
  }
  
  platform :ios, '10.0'
  use_frameworks!
  
  # Yep.
  inhibit_all_warnings!
  
  target 'Kiosk' do
  
    # Artsy stuff
    pod 'Artsy+UIColors'
    pod 'Artsy+UILabels'
    pod 'Artsy-UIButtons'
  
    if ENV['ARTSY_STAFF_MEMBER'] != nil || ENV['CI'] != nil
        pod 'Artsy+UIFonts'
    else
        pod 'Artsy+OSSUIFonts'
    end
  
    pod 'ORStackView', '2.0'
    pod 'FLKAutoLayout', '0.1.1'
    pod 'ARCollectionViewMasonryLayout', '~> 2.0.0'
    pod 'SDWebImage', '~> 3.7'
    pod 'SVProgressHUD'
    
    # Required as a workaround for https://github.com/bitstadium/HockeySDK-iOS/pull/421
    pod 'HockeySDK-Source', git: 'https://github.com/bitstadium/HockeySDK-iOS.git'
    pod 'ARAnalytics/Segmentio'
    pod 'ARAnalytics/HockeyApp'
  
    pod 'CardFlight-v4'
    pod 'Stripe', '14.0.1'
    pod 'ECPhoneNumberFormatter'
    pod 'UIImageViewAligned', :git => 'https://github.com/ashfurrow/UIImageViewAligned.git'
    pod 'DZNWebViewController', :git => 'https://github.com/orta/DZNWebViewController.git'
    pod 'ReachabilitySwift'
  
    pod 'UIView+BooleanAnimations'
    pod 'ARTiledImageView'
    pod 'XNGMarkdownParser'
    pod 'ISO8601DateFormatter'
  
    # Swift pods
    pod 'SwiftyJSON'
    pod 'RxSwift'
    pod 'RxCocoa'
    pod 'RxOptional'
    pod 'Moya/RxSwift'
    pod 'NSObject+Rx'
    pod 'Action'
  
    target 'KioskTests' do
      inherit! :search_paths
  
      pod 'FBSnapshotTestCase'
      pod 'Nimble-Snapshots'
      pod 'Quick'
      pod 'Nimble'
      pod 'RxNimble'
      pod 'Forgeries'
      pod 'RxBlocking'
  
    end
  end
  

总结

CocoaPods的配置并不复杂,但是我觉得创建一个CocoaPods库倒是挺复杂的,毕竟我们有可能创建自己的私有库,然后引用来使用,这个我还不知道怎么创建,虽然官网有介绍文档,以后有需要再研究吧。

本文就先到这里了,感觉没说到什么特别重点的内容,因为我之前已经学过一些了。另外有一个问题,发现Podfile的本质是一个Ruby脚本,这些难以阅读理解的脚本其实就是Ruby代码,这让我想起Gradle和Groovy,我挺讨厌使用复杂语言的配置文件,要投入好多时间和精力!

但是还是得学一下Ruby,不多说了,先学一下Ruby!

木子山

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: