前语

关于大多数软件开发团队来说,依靠办理东西必不可少,它能针对开源和私有依靠进行装置与办理,从而提升开发功率,下降保护本钱。针对不同的言语与渠道,其依靠办理东西也各有不同,例如 npm 办理 Javascript、Gradle 、Maven 办理 Jar 包、pip 办理 Python 包,Bundler、RubyGems 等等。本文聚集于 iOS 方面,对 CocoaPods 的运用和部分原理进行论述。

简略易用的 CocoaPods

关于 iOSer 来说,CocoaPods 并不陌生,简直一切的 iOS 工程都会有它的身影。CocoaPods 选用 Ruby 构建,它是 Swift 和 Objective-C Cocoa 项目的依靠办理东西。在 MacOS 上,引荐运用默许的 Ruby 进行装置 (以下操作均在 CocoaPods 1.10.1、Ruby 2.7.2 进行):

sudo gem install cocoapods

假如装置成功,便能够运用 pod 的相关指令了。针对一个简略的项目来说,只需三步便可引进其他的依靠:

  1. 创立 Podfile 文件( CocoaPods 供给了 pod init 指令创立)
  2. 对 Podfile 文件进行编写,增加依靠的库,版别等信息。
  3. 在指令行履行 pod install 指令

顺畅的话,这时在项目目录下会出现以下文件:

  • .xcworkspace:CocoaPods 将项目分为了主工程与依靠工程(Pods)。与 .xcodeproj 比较 .xcworkspace 关于办理多个项目的才能更强,你也能够将复杂的大型运用转换为以 .xcworkspace 构建的多个兄弟项目,从而更轻松的保护和同享功用。
  • Podfile.lock:记载并跟踪依靠库版别,将依靠库确定于某个版别。
  • Pods 文件夹:寄存依靠库代码。
  • Pods/Manifest.lock:每次 pod install 时创立的 Podfile.lock 的副本,用于比较这两个文件。一般来说,Podfile.lock 会归入版别操控办理,而 Pods 文件夹则不会归入版别操控改变;这意味着 Podfile.lock 表明项目应该依靠的库版别信息,而 Manifest.lock 则代表本地 Pods 的依靠库版别信息。在 pod install 后会将脚本插入到 Build Phases,名为 [CP] Check Pods Manifest.lock,从而确保开发者在运转 app 之前能够更新 Pods,以确保代码是最新的。

pod install vs. pod update

  • pod install:在每一次修正 Podfile 以增加、更新或删去 pod 时运用。它会下载并装置新的 Pod,并将其版别信息写入 Podfile.lock 中。
  • pod outdated:列出一切比 Podfile.lock 中当时记载的版别 newer 版别的 pod。
  • pod update [PODNAME]:CocoaPods 会查找 newer 版别的 PODNAME,一起将 pod 更新到或许的最新版别(须符合 Podfile 限制)。若没有 PODNAME,则会将每一个 pod 更新到或许的最新版别。

一般来说,每次修正 Podfile 时运用 pod install,仅在需求更新某个 pod 版别(一切版别)时才运用 pod update。一起,需提交 Podfile.lock 文件而不是 Pods 文件夹来到达同步一切 pod 版别的目的。

ps: newer 代表愈加新的,若选用中文了解起来比较别扭。

Podfile 语法标准

Podfile 描绘了一个或多个 Xcode 项目的 target 依靠联系,它是一种 DSL,了解它对咱们运用好 CocoaPods 是一个必不可少的步骤。下面列出其相关的语法标准:

Root Options

install!:指定 CocoaPods 装置 Podfile 时运用的装置办法和选项。如:

install! 'cocoapods',
    :deterministic_uuids => false,
    :integrate_targets => false
  • :clean:依据 podspec 和项目支撑渠道的指定,整理一切不被 pod 运用的文件,默许为 true。
  • :deduplicate_targets:是否对 pod target 进行重复数据删去,默许为 true。
  • :deterministic_uuids:创立 pod project 是否发生确定性 UUID,默许为 true。
  • :integrate_targets:是否承继到用户项目中,为 false 会将 Pod 下载并装置到到 project_path/Pods 目录下,默许为 true。
  • :lock_pos_sources:是否确定 pod 的源文件,当 Xcode 尝试修正时会提示解锁文件,默许为 true。
  • :warn_for_multiple_pod_sources:当多个 source 包含同名同版别 pod 时是否宣布正告,默许为 true。
  • :warn_for_unused_master_specs_repo:假如没有明确指出 master specs repo 的 git 是否宣布正告,默许为 true。
  • :share_schemes_for_development_pods:是否为开发中的 pod 共享 schemes,默许为 false。
  • :disable_input_output_paths:是否禁用 CocoaPods 脚本阶段的输入输出途径(Copy Frameworks 和 Copy Resources),默许为 false。
  • :preserve_pod_file_structure:是否保存一切 pod 的文件结构,默许为 false。
  • :generate_multiple_pod_projects:是否为每一个 pod target 生成 一个 project,生成与 Pods/Pods 文件夹中,默许为 false。
  • :incremental_installation:仅对自前次装置的 target 与其相关的 project 的改变部分进行从头生成,默许为 false。
  • :skip_pods_project_generation:是否跳过生成 Pods.xcodeproj 并仅进行依靠项解析与下载,默许为 false。 ensure_bundler!:当 bundler 版别不匹配时宣布正告。
ensure_bundler! '~> 2.0.0'

Dependencies

pod:指定项目的依靠项

  • 依靠版别操控:=、>、>=、<、<= 为字面意思;~> 0.1.2 表明 0.1.2 <= currVersion < 0.2 之间的符合要求的最新版别版别。
  • Build configurations:默许依靠装置在一切的构建装备中,但也可仅在指定构建装备中启用。
  • Modular Headers:用于将 pod 转换为 module 以支撑模块,这时在 Swift 中能够不必凭借 bridging-header 桥接就能够直接导入,简化了 Swift 引证 Objective-C 的方法;也能够选用 use_modular_headers! 进行大局的改变。
  • Source:指定具有依靠项的源,一起会忽略大局源。
  • Subspecs:默许会装置一切的 subspecs,但可拟定装置某些 subspecs。
  • Test Specs:默许不会装置 test specs,但可选择性装置 test specs。
  • Local path:将开发的 pod 与其客户端一起运用,可选用 path。
  • 指定某个特别或许更为先进的 pod 版别
# 依靠版别操控
pod 'Objection', '~> 0.9'
# Build configurations
pod 'PonyDebugger', :configurations => ['Debug', 'Beta']
# Modular Headers
pod 'SSZipArchive', :modular_headers => true
# Source
pod 'PonyDebugger', :source => 'https://github.com/CocoaPods/Specs.git'
# Subspecs
pod 'QueryKit', :subspecs => ['Attribute', 'QuerySet']
# Test Specs
pod 'AFNetworking', :testspecs => ['UnitTests', 'SomeOtherTests']
# Local path
pod 'AFNetworking', :path => '~/Documents/AFNetworking'
# 指定某个特别或许更为先进的 Pod 版别
pod 'AFNetworking', :git => 'https://github.com/gowalla/AFNetworking.git', :branch => 'dev'
pod 'AFNetworking', :git => 'https://github.com/gowalla/AFNetworking.git', :tag => '0.7.0'
pod 'AFNetworking', :git => 'https://github.com/gowalla/AFNetworking.git', :commit => '082f8319af'
# 指定某个 podspec
pod 'JSONKit', :podspec => 'https://example.com/JSONKit.podspec'

inherit:设置当时 target 的承继方法。

:complete 承继父级 target 的一切行为,:none 不承继父级 target 的任何行为,:search_paths 仅承继父级的查找途径。

target 'App' do
 target 'AppTests' do
  inherit! :search_paths
 end
end

target:与 Xcode 中的 target 相对应,block 中是 target 的依靠项。

默许情况下,target 包含在父级 target 界说的依靠项,也即 inherit!:complete。关于 :complete:search_paths:complete 会复制父级 target 的 pod 副本,而 :search_paths 则只进行 FRAMEWORK_SEARCH_PATHSHEADER_SEARCH_PATHS 的相关复制,详细可经过比对 Pods/Target Support Files 的相关文件得以验证,一般在 UnitTests 中运用,以减少多余的 install_framework 过程。

target 'ShowsApp' do
 pod 'ShowsKit'
 # 具有 ShowsKit 和 ShowTVAuth 的复制
 target 'ShowsTV' do
  pod 'ShowTVAuth'
 end
 # 具有 Specta 和 Expecta 的复制
 # 而且能够经过 ShowsApp 进行访问 ShowsKit, 相当于 ShowsApp 是 ShowsTests 的宿主APP
 target 'ShowsTests' do
  inherit! :search_paths
  pod 'Specta'
  pod 'Expecta'
 end
end

abstract_target:界说 abstract_target,便利 target 进行依靠承继,在 CocoaPods 1.0 版别之前为 link_with

abstract_target 'Networking' do
 pod 'AlamoFire'
 target 'Networking App 1'
 target 'Networking App 2'
end

abstract:表明当时 target 是抽象的,不会链接到 Xcode 的 target 中。

script_phase:增加脚本阶段。

在履行完 pod install 之后 CocoaPods 会将脚本增加到对应的 target build phases

target 'App' do
script_phase {
:name => 'scriptName' # 脚本称号,
    :script => 'echo "nihao"' # 脚本内容,
    :execution_position => :before_compile / :after_compile
    :shell_path => '/usr/bin/ruby' # 脚本途径
    :input_files => ['/input/filePath'], # 输入文件
    :output_files => ['/outpput/filePath'] # 输出文件
}
end

Target configuration

platform:指定其构建渠道。

默许值为 iOS 4.3、OSX 10.6、tvOS 9.0 和 watchOS 2.0。CocoaPods 1.0 之前的版别为 xcodeproj

platform :ios, '4.0'

project:指定包含 target 的 Xcode project。这一般在 workspace 存在多个 xcode project 中运用:

# 在 FastGPS Project 中能够找到一个名为 MyGPSApp 的 target
target 'MyGPSApp' do
 project 'FastGPS'
 ...
end

inhibit_all_warnings!:制止一切正告。假如针对单个 Pod,则能够选用:

pod 'SSZipArchive', :inhibit_warnings => true
pod 'SSZipArchive', :inhibit_warnings => true

user_modular_headers!:将一切 Pod 模块化。假如针对单个 Pod,则能够选用:

pod 'SSZipArchive', :modular_headers => true
pod 'SSZipArchive', :modular_headers => false

user_frameworks!:选用 framework 而不是 .a 文件的静态库。 能够经过 :linkage 指定运用静态库仍是动态库:

use_frameworks!:linkage => :dynamic / :static

supports_swift_versions:指定 target definition 支撑的 swift 版别要求

supports_swift_versions '>= 3.0', '< 4.0'

Workspace

workspace:指定包含一切项目的 Xcode workspace。

workspace 'MyWorkspace'

Sources

sources:Podfile 从指定的源列表中进行检索。sources 默许存储在 ~/.cocoapods/repos 中,是大局的而非按 target definition 存储。当有多个相同的 Pod 时,优先选用检索到的 Pod 的第一个源,因而当指定另一个来历时,则需显现指定 CocoaPods 的源。

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

Hooks

plugin:指定在装置期间运用的插件。

plugin 'cocoapods-keys', :keyring => 'Eidolon'
plugin 'slather'

pre_install:在下载后和在装置 Pod 前进行更改。

pre_install do |installer|
 # Do something fancy!
end

pre_integrate:在 project 写入磁盘前进行更改。

pre_integrate do |installer|
 # perform some changes on dependencies
end

post_install:对生成 project 写入磁盘前进行终究的修正。

post_install do |installer|
 installer.pods_project.targets.each do |target|
  target.build_configurations.each do |config|
   config.build_settings['GCC_ENABLE_OBJC_GC'] = 'supported'
  end
 end
end

post_integrate:在 project 写入磁盘后进行终究更改。

post_integrate do |installer|
 # some change after project write to disk
end

podspec 语法标准

podspec = pod Specification,意为 pod 标准,它是一个 Ruby 文件。包含了 Pod 的库版别详细信息,例如应从何处获取源、运用哪些文件、要运用构建设置等信息;也能够看作该文件是整个库房的索引文件,了解它对咱们知道 Pod 库是怎么安排、运作的供给了很大协助。podspec 的 DSL 供给了极大的灵活性,文件可经过 pod spec create 创立。

Root

称号 用途 必需
name pod 称号 required
version pod 版别,遵从语义化版别操控 required
swift_version 支撑的 Swift 版别
cocoapods_version 支撑的 CocoaPods 版别
authors pod 保护者的名字和电子邮件,用“, ”进行切割 required
license pod 的许可证 required
homepage pod 主页的 URL required
source 源地址,即源文件的寄存地址,支撑多种方法源 required
summary pod 的简略描绘 required
prepare_command 下载 pod 后履行的 bash 脚本
static_framework 是否选用静态 framework 分发
deprecated 该库是否已被弃用
deprecated_in_favor_of 该库称号已被弃用,取而代之
Pod::Spec.new do |s|
 s.name       = 'CustomPod'
 s.version     = '0.1.0'
 s.summary     = 'A short description of CustomPod.'
 s.swift_versions  = ['3.0', '4.0', '4.2']
 s.cocoapods_version = '>= 0.36'
 s.author      = { 'nihao' => 'XXXX@qq.com' }
 s.license     = { :type => 'MIT', :file => 'LICENSE' }
 s.homepage     = 'https://github.com/XXX/CustomPod'
# Supported Key
# :git=> :tag, :branch, :commit,:submodules
# :svn=> :folder, :tag,:revision
# :hg=>:revision
# :http=> :flatten, :type, :sha256, :sha1,:headers
 s.source      = { :git => 'https://github.com/XX/CustomPod.git', :tag => s.version.to_s }
 s.prepare_command = 'ruby build_files.rb'
 s.static_framework = true
 s.deprecated    = true
 s.deprecated_in_favor_of = 'NewMoreAwesomePod'
end

Platform

platform:pod 支撑的渠道,留空意味着 pod 支撑一切渠道。当支撑多渠道时应该用 deployment_target 代替。

spec.platform = :osx, '10.8'

deployment_target:允许指定支撑此 pod 的多个渠道,为每个渠道指定不同的部署目标。

spec.ios.deployment_target = '6.0'
spec.osx.deployment_target = '10.8'

Build settings

dependency:基于其他 pods 或子标准的依靠

spec.dependency 'AFNetworking', '~> 1.0', :configurations => ['Debug']

info_plist:加入到生成的 Info.plist 的键值对,会对 CocoaPods 生成的默许值进行掩盖。仅对运用 framework 的结构有影响,对静态库无效。关于运用标准,这些值将合并到运用程序主机的 Info.plist;关于测验标准,这些值将合并到测验包的 Info.plist。

spec.info_plist = {
 'CFBundleIdentifier' => 'com.myorg.MyLib',
 'MY_VAR' => 'SOME_VALUE'
}

requires_arc:允许指定哪些 source_files 选用 ARC,不运用 ARC 的文件将具有 -fno-objc-arc 编译器标志

spec.requires_arc = false
spec.requires_arc = 'Classes/Arc'
spec.requires_arc = ['Classes/*ARC.m', 'Classes/ARC.mm']

frameworks:运用者 target 需求链接的体系结构列表

spec.ios.framework = 'CFNetwork'
spec.frameworks = 'QuartzCore', 'CoreData'

weak_frameworks:运用者 target 需求弱链接的结构列表

spec.weak_framework = 'Twitter'
spec.weak_frameworks = 'Twitter', 'SafariServices'

libraries:运用者 target 需求链接的体系库列表

spec.ios.library = 'xml2'
spec.libraries = 'xml2', 'z'

compiler_flags:应传递给编译器的 flags

spec.compiler_flags = '-DOS_OBJECT_USE_OBJC=0', '-Wno-format'

pod_target_xcconfig:将指定 flag 增加到终究 pod 的 xcconfig 文件

spec.pod_target_xcconfig = { 'OTHER_LDFLAGS' => '-lObjC' }

user_target_xcconfig: 将指定 flag 增加到终究聚合的 target 的 xcconfig,不引荐运用此特点,由于会污染用户的构建设置,或许会导致抵触。

spec.user_target_xcconfig = { 'MY_SUBSPEC' => 'YES' }

prefix_header_contents: 在 Pod 中注入的预编译内容,不引荐运用此特点,由于其会污染用户或许其他库的预编译头。

spec.prefix_header_contents = '#import <UIKit/UIKit.h>', '#import <Foundation/Foundation.h>'

prefix_header_file:预编译头文件,false 表明不生成默许的 CocoaPods 的与编译头文件。 不引荐运用途径方法,由于其会污染用户或许其他库的预编译头。

spec.prefix_header_file = 'iphone/include/prefix.pch'
spec.prefix_header_file = false

module_name:生成的 framrwork / clang module 运用的称号,而非默许称号。

spec.module_name = 'Three20'

header_dir:存储头文件的目录,这样它们就不会被破坏。

spec.header_dir = 'Three20Core'

header_mappings_dir:用于保存头文件文件夹的目录。如未供给,头文件将被碾平。

spec.header_mappings_dir = 'src/include'

script_phases:该特点允许界说脚本在 pod 编译时履行,其作为 xcode build 指令的一部分履行,还能够运用编译期间所设置的环境变量。

spec.script_phases = [
  { :name => 'Hello World', :script => 'echo "Hello World"' },
  { :name => 'Hello Ruby World', :script => 'puts "Hello World"', :shell_path => '/usr/bin/ruby' },
 ]

File patterns

文件方法指定了库的一切文件办理方法,如源代码、头文件、framework、libaries、以及各种资源。其文件方法通配符方法可参阅 LINK。

source_files:指定源文件

spec.source_files = 'Classes/**/*.{h,m}', 'More_Classes/**/*.{h,m}'

public_header_files:指定公共头文件,这些头文件与源文件匹配,并生成文档向用户供给。假如未指定,则将 source_files 中的一切头文件都包含生成。

spec.public_header_files = 'Headers/Public/*.h'

project_header_files:指定项目头文件,与公共头文件相对应,以排除不该向用户项目揭露且不运用于生成文档的标头,且不会出现在构建目录中。

spec.project_header_files = 'Headers/Project/*.h'

private_header_files:私有头文件,与公共头文件对应,以排除不该向用户项目揭露且不运用于生成文档的标头,这些头文件会出现在产品中的 PrivateHeader 文件夹中。

spec.private_header_files = 'Headers/Private/*.h'

vendered_frameworks:pod 附加的 framework 途径

spec.ios.vendored_frameworks = 'Frameworks/MyFramework.framework'
spec.vendored_frameworks = 'MyFramework.framework', 'TheirFramework.xcframework'

vendered_libraries:pod 附加的 libraries 途径

spec.ios.vendored_library = 'Libraries/libProj4.a'
spec.vendored_libraries = 'libProj4.a', 'libJavaScriptCore.a'

on_demand_resources:依据 Introducing On demand Resources 按需加载资源,不引荐与主工程同享标签,默许类别为 category => :download_on_demand

s.on_demand_resources = {
 'Tag1' => { :paths => ['file1.png', 'file2.png'], :category => :download_on_demand }
}
s.on_demand_resources = {
 'Tag1' => { :paths => ['file1.png', 'file2.png'], :category => :initial_install }
}

resources:为 pod 构建的 bundle 的称号和资源文件,其间 key 为 bundle 称号,值代表它们运用的文件方法。

spec.resource_bundles = {
'MapBox' => ['MapView/Map/Resources/*.png'],
  'MapBoxOtherResources' => ['MapView/Map/OtherResources/*.png']
}

exclude_files:排除的文件方法列表

spec.ios.exclude_files = 'Classes/osx'
spec.exclude_files = 'Classes/**/unused.{h,m}'

preserve_paths:下载后不该删去的文件。默许情况下,CocoaPods 会删去与其他文件方法不匹配的一切文件

spec.preserve_path = 'IMPORTANT.txt'
spec.preserve_paths = 'Frameworks/*.framework'

module_map:pod 承继为 framework 时运用的模块映射文件,默许为 true,CocoaPods 依据 公共头文件创立 module_map 文件。

spec.module_map = 'source/module.modulemap'
spec.module_map = false

Subspecs

subspec:子模块的标准;实行两层承继:specs 自动承继一切 subspec 作为依靠项(除非指定默许 spec);subspec 承继了父级的特点;

# 选用不同源文件的 Specs, CocoaPods 自动处理重复引证问题
subspec 'Twitter' do |sp|
 sp.source_files = 'Classes/Twitter'
end
subspec 'Pinboard' do |sp|
 sp.source_files = 'Classes/Pinboard'
end
# 引证其他子标准
s.subspec "Core" do |ss|
  ss.source_files = "Sources/Moya/", "Sources/Moya/Plugins/"
  ss.dependency "Alamofire", "~> 5.0"
  ss.framework = "Foundation"
 end
 s.subspec "ReactiveSwift" do |ss|
  ss.source_files = "Sources/ReactiveMoya/"
  ss.dependency "Moya/Core"
  ss.dependency "ReactiveSwift", "~> 6.0"
 end
 s.subspec "RxSwift" do |ss|
  ss.source_files = "Sources/RxMoya/"
  ss.dependency "Moya/Core"
  ss.dependency "RxSwift", "~> 5.0"
 end
end
# 嵌套子标准
Pod::Spec.new do |s|
 s.name = 'Root'
 s.subspec 'Level_1' do |sp|
  sp.subspec 'Level_2' do |ssp|
  end
 end
end

default_subspecs:默许子标准数组称号,不指定将全部子标准作为默许子标准,:none 表明不需求任何子标准。

spec.default_subspec = 'Core'
spec.default_subspecs = 'Core', 'UI'
spec.default_subspecs = :none

scheme:用以给指定 scheme configuration 增加拓展

spec.scheme = { :launch_arguments => ['Arg1'] }
spec.scheme = { :launch_arguments => ['Arg1', 'Arg2'], :environment_variables => { 'Key1' => 'Val1'} }

test_spec:测验标准,在 1.8 版别支撑。可参阅:CocoaPods 1.8 Beta

requires_app_host:是否需求宿主 APP 运转测验,仅适用于测验标准。

app_host_name:必要时作用于运用程序的运用程序标准称号

app_spec:宿主 APP 标准

Pod::Spec.new do |s|
 s.name     = 'CannonPodder'
 s.version   = '1.0.0'
 # ...rest of attributes here
 s.app_spec 'DemoApp' do |app_spec|
  app_spec.source_files = 'DemoApp/**/*.swift'
  # Dependency used only by this app spec.
  app_spec.dependency 'Alamofire'
 end
 s.test_spec 'Tests' do |test_spec|
  test_spec.requires_app_host = true
  # Use 'DemoApp' as the app host.
  test_spec.app_host_name = 'CannonPodder/DemoApp'
  # ...rest of attributes here
  # This is required since 'DemoApp' is specified as the app host.
  test_spec.dependency 'CannonPodder/DemoApp'
 end
end

Multi-Platform support

存储特定于某一个渠道的值,别离为 ios、osx、macOS、tvos、watchos:

spec.resources = 'Resources/**/*.png'
spec.ios.resources = 'Resources_ios/**/*.png'

Pod 的开发流程

了解完 Podfile 和 podspec 的相关的标准之后,那么开发自己的 pod 应该是一件驾轻就熟的事。

Spec Repo

Spec Repo 是 podspec 的库房,即是存储相关的 podspec 文件的当地。本地源存储于 ~/.cocoapods/repos中,它从 git 上拉取并彻底保存目录结构。能够发现, Master Specs Repo 的现在目录结构有些特别;以往版别的 Master Spec Repo 是彻底在同一目录下的,但若很多文件在同一目录中会导致了 Github 下载慢 的问题。为处理这个问题,选用散列表方法处理。详细方法为对称号进行 MD5 核算得到散列值,取前三位作为目录前缀,以对文件分散化。初度之外,CocoaPods 后续还选用 CDN 以及 trunk 进一步加速下载速度,有爱好能够参阅 CocoaPods Source 办理机制。

如:md5("CJFoundation") => 044d913fdd5a52b303222c357521f744CJFoundation 则在 /Specs/0/4/4 目录中

CocoaPods使用指南

Create

只需运用 pod lib create [PodName] 指令便能够快速创立一个自己的 pod 。填写好运用渠道、运用言语、是否包含 Demo、测验结构等信息,CocoaPods 会从默许的 Git 地址中拉取一份 pod 模版,一起也能够经过 --template-url=URL 指定模版地址。在履行完后,整个文件结构如下:

tree CustomPod -L 2
CustomPod
├── CustomPod
  ├── Assets // 寄存资源文件
  └── Classes
    └── RemoveMe.[swift/m] // 单一文件以确保开始编译作业
├── CustomPod.podspec // Pod 的 spec 文件, 是一个 Pod 依靠的索引以及标准信息
├── Example // 用作演示/测验的示例项目
  ├── CustomPod
  ├── CustomPod.xcodeproj
  ├── CustomPod.xcworkspace
  ├── Podfile
  ├── Podfile.lock
  ├── Pods
  └── Tests
├── _Pods.xcodeproj -> Example/Pods/Pods.xcodeproj // 指向 Pods 项目的以取得 Carthage 支撑
├── LICENSE // 许可证
└── README.md // 自述文件

Development

将源文件和资源别离放入 Classes / Assets 文件夹中,或许按你喜欢的方法安排文件,并在 podspec 文件中修正相应项。假如你有任何想运用的装备项,可参阅前面的podsepc 语法标准 。

一般来说,开发 Pod 一般都是作为本地 Pod 被其他 Project 所依靠进行开发,不管是运用 example 文件夹的 project 或许其他的 Project。

pod 'Name', :path => '~/CustomPod/'

Testing

经过 pod lib lint 以验证 Pod 库房的运用是否正常。

Release

前面提到过 podspec 能够看作是整个库房的索引文件,有了这个文件也就能安排起一个 Pod。因而官方的源以及私有源都只需求 podspec 即可,而其他文件则应推送到 podspec 中 source 中指定库房,这个库房应该是你自创立的。

在预备发布推送源代码时,需求更新版别号以及在 git 上打上 tag,这是为了进行版别号匹配,由于默许情况下的 podspec 文件中:

s.source = { :git => 'https://github.com/XXX/CustomPod.git', :tag => s.version.to_s }

或许你的作业流操作如下:

$ cd ~/code/Pods/NAME
$ edit NAME.podspec
# set the new version to 0.0.1
# set the new tag to 0.0.1
$ pod lib lint
$ git add -A && git commit -m "Release 0.0.1."
$ git tag '0.0.1'
$ git push --tags

存有几种方法推送 podspec 文件:

  1. 推送到公共库房,需求用到的 trunk 子指令,更多能够参阅 Getting setup with Trunk:
# 经过电子邮箱进行注册
pod trunk register orta@cocoapods.org 'Orta Therox' --description='macbook air'
# 将指定podspec文件推送到公共库房中
pod trunk push [NAME.podspec]
# 增加其他人作为协作者
pod trunk add-owner ARAnalytics kyle@cocoapods.org
  1. 推送到私有源,例如 Artsy/Specs,需求用到 repo 子指令,更多能够参阅 Private Pods:
# 将私有源地址增加到本地
pod repo add REPO_NAME SOURCE_URL
# 查看私有源是否装置成功并预备就绪
cd ~/.cocoapods/repos/REPO_NAME
pod repo lint .
# 将Pod的podspec增加到指定REPO_NAME中
pod repo push REPO_NAME SPEC_NAME.podspec
  1. 不推送到任何源中,若能存在以 URL 方法检索到 podspec文件,则可用该 URL,一般选用库房地址,例如:
pod 'AFNetworking', :git => 'https://github.com/XXX/CustomPod.git'

Semantic Versioning

语义化版别操控顾名思义是一种语义上的版别操控,它不要求强制遵从,仅仅期望开发者能够尽量恪守。假如库之间依靠联系过高,或许面临版别操控被锁死的危险(或许需求对每一个依靠库改版才能完结某次升级);假如库之间依靠联系过于松散,又将无法避免版别的紊乱(或许库兼容性不再能支撑以往版别),语义化版别操控正是作为这个问题的处理计划之一。不管在 CocoaPods 中,仍是 Swift Packager Manager 上,官方都期望库开发者的的版别号能遵从这一准则:

例如,给定版别号 MAJOR.MINOR.PATCH

  1. MAJOR:进行不兼容的 API 更改时进行修正
  2. MINOR:向后兼容的方法增加新功用时进行修正
  3. PATCH:进行向后兼容的过错修正时进行修正

先行版别号以及版别编译信息能够增加到 MAJOR.MINOR.PATCH 后边以作为延伸。

CocoaPods 原理浅析

CococaPods 中心组件

CocoaPods 被 Ruby 办理,其中心部分也被分为一个一个组件。下载源码,能够看到 Gemfile 文件如下,其依靠了若干个 gem,有意思的是 cp_gem 函数,经过 SKIP_UNRELEASED_VERSIONSpath 来操控是否选用本地的 gem 途径,实现了 DEVELOPMENT 与 RELEASE 环境的切换。

SKIP_UNRELEASED_VERSIONS = false
# Declares a dependency to the git repo of CocoaPods gem. This declaration is
# compatible with the local git repos feature of Bundler.
def cp_gem(name, repo_name, branch = 'master', path: false)
 return gem name if SKIP_UNRELEASED_VERSIONS
 opts = if path
     { :path => "../#{repo_name}" }
    else
     url = "https://github.com/CocoaPods/#{repo_name}.git"
     { :git => url, :branch => branch }
    end
 gem name, opts
end
source 'https://rubygems.org'
gemspec
group :development do
 cp_gem 'claide',        'CLAide'
 cp_gem 'cocoapods-core',    'Core'
 cp_gem 'cocoapods-deintegrate', 'cocoapods-deintegrate'
 cp_gem 'cocoapods-downloader', 'cocoapods-downloader'
 cp_gem 'cocoapods-plugins',   'cocoapods-plugins'
 cp_gem 'cocoapods-search',   'cocoapods-search'
 cp_gem 'cocoapods-trunk',    'cocoapods-trunk'
 cp_gem 'cocoapods-try',     'cocoapods-try'
 cp_gem 'molinillo',       'Molinillo'
 cp_gem 'nanaimo',        'Nanaimo'
 cp_gem 'xcodeproj',       'Xcodeproj'
 gem 'cocoapods-dependencies', '~> 1.0.beta.1'
 ...
end

这些组件相对独立,被分红一个一个 Gem 包,在 Core Components 中,能够找到对这些组件的简要描绘。一起也能够到 CocoaPods 的 Github 中去看详细文档。

CocoaPods使用指南
  • CocoaPods:指令行支撑与装置程序,也会处理 CocoaPods 的一切用户交互。
  • cocoapods-core:对模版文件的解析,如 Podfile、.podspec 等文件。
  • CLAide:一个简略的指令解析器,它供给了一个快速创立功用完全的指令行界面的 API。
  • cocoapods-downloader:用于下载源码,为各种类型的源代码操控器(HTTP/SVN/Git/Mercurial) 供给下载器。它供给 tags、commites、revisions、branches 以及 zips 文件的下载与解压缩操作。
  • Monlinillo:CocoaPods:关于依靠裁定算法的封装,它是一个具有前项检察的回溯算法。不仅在 pods 中,Bundler 和 RubyGems 也是运用这一套裁定算法。
  • Xcodeproj:经过 Ruby 来对 Xcode projects 进行创立于修正。如:脚本办理、libraries 构建、Xcode workspece 和装备文件的办理。
  • cocoapods-plugins:插件办理,其间有 pod plugins 指令协助你获取的可用插件列表以及开发一个新插件等功用,详细可用 pod plugins --help 了解。

pod install 做了什么

履行 pod install --verbose,会显现 pod install 过程中的更多 debugging 信息。下文首要参阅:全体掌握 CocoaPods 中心组件

经过音讯转发与 CLAide 指令解析,终究调用了 CocoaPods/lib/cocoapods/installer.rb 的 install! 函数,首要流程图如下:

CocoaPods使用指南
def install!
prepare
resolve_dependencies
download_dependencies
validate_targets
clean_sandbox
if installation_options.skip_pods_project_generation?
show_skip_pods_project_generation_message
run_podfile_post_install_hooks
else
integrate
end
write_lockfiles
perform_post_install_actions
end

1. Install 环境预备(prepare)

def prepare
 # 假如检测出当时目录是 Pods,直接 raise 停止
 if Dir.pwd.start_with?(sandbox.root.to_path)
  message = 'Command should be run from a directory outside Pods directory.'
  message << "\n\n\tCurrent directory is #{UI.path(Pathname.pwd)}\n"
  raise Informative, message
 end
 UI.message 'Preparing' do
  # 假如 lock 文件的 CocoaPods 主版别和当时版别不同,将以新版别的装备对 xcodeproj 工程文件进行更新
  deintegrate_if_different_major_version
  # 对 sandbox(Pods) 目录建立子目录结构
  sandbox.prepare
  # 检测 PluginManager 是否有 pre-install 的 plugin
  ensure_plugins_are_installed!
  # 履行插件中 pre-install 的一切 hooks 办法
  run_plugins_pre_install_hooks
 end
end

在 prepare 阶段会完结 pod install 的环境预备,包含目录结构、版别一致性以及 pre_install 的 hook。

2. 处理依靠抵触(resolve dependencies)

def resolve_dependencies
  # 获取 Sources
  plugin_sources = run_source_provider_hooks
  # 创立一个 Analyzer
  analyzer = create_analyzer(plugin_sources)
  # 假如带有 repo_update 标记
  UI.section 'Updating local specs repositories' do
    # 履行 Analyzer 的更新 Repo 操作
    analyzer.update_repositories
  end if repo_update?
  UI.section 'Analyzing dependencies' do
    # 从 analyzer 取出最新的剖析结果,@analysis_result,@aggregate_targets,@pod_targets
    analyze(analyzer)
    # 拼写过错降级辨认,白名单过滤
    validate_build_configurations
  end
  # 假如 deployment? 为 true,会验证 podfile & lockfile 是否需求更新
  UI.section 'Verifying no changes' do
    verify_no_podfile_changes!
    verify_no_lockfile_changes!
  end if deployment?
  analyzer
end

经过 Podfile、Podfile.lock 以及 manifest.lock 等生成 Analyzer 对象,其内部会运用个 Molinillo 算法解析得到一张依靠联系表,进行一系列的剖析与依靠抵触处理。

3. 下载依靠文件(download dependencies)

def download_dependencies
 UI.section 'Downloading dependencies' do
  # 构造 Pod Source Installer
  install_pod_sources
  # 履行 podfile 界说的 pre install 的 hooks
  run_podfile_pre_install_hooks
  # 依据装备整理 pod sources 信息,首要是整理无用 platform 相关内容
  clean_pod_sources
 end
end

经过前面剖析与处理依靠抵触后,这是会进行依靠下载。会依据依靠信息是否被新增加或许修正等信息进行下载,一起下载后也会在本地留有一份缓存,其目录在 ~/Library/Caches/CocoaPods 。

4. 验证 targets(validate targets)

def validate_targets
  validator = Xcode::TargetValidator.new(aggregate_targets, pod_targets, installation_options)
  validator.validate!
end
def validate!
  verify_no_duplicate_framework_and_library_names
  verify_no_static_framework_transitive_dependencies
  verify_swift_pods_swift_version
  verify_swift_pods_have_module_dependencies
  verify_no_multiple_project_names if installation_options.generate_multiple_pod_projects?
end
  • verify_no_duplicate_framework_and_library_names:验证是否有重名的 framework / library
  • verify_no_static_framework_transitive_dependencies:验证动态库是否有静态链接库依靠。个人以为,这个验证是不必要的,最少不必要 error。
  • verify_swift_pods_swift_version:验证 Swift pod 的 Swift 版别装备且彼此兼容
  • verify_swift_pods_have_module_dependencies:验证 Swift pod 是否支撑 module
  • verify_no_multiple_project_names:验证没有重名的 project 称号

5. 生成工程(Integrate)

def integrate
  generate_pods_project
  if installation_options.integrate_targets?
    # 集成用户装备,读取依靠项,运用 xcconfig 来装备
    integrate_user_project
  else
    UI.section 'Skipping User Project Integration'
  end
end
def generate_pods_project
  # 创立 stage sanbox 用于保存装置前的沙盒状态,以支撑增量编译的比照
  stage_sandbox(sandbox, pod_targets)
  # 查看是否支撑增量编译,假如支撑将返回 cache result
  cache_analysis_result = analyze_project_cache
  # 需求从头生成的 target
  pod_targets_to_generate = cache_analysis_result.pod_targets_to_generate
  # 需求从头生成的 aggregate target
  aggregate_targets_to_generate = cache_analysis_result.aggregate_targets_to_generate
  # 整理需求从头生成 target 的 header 和 pod folders
  clean_sandbox(pod_targets_to_generate)
  # 生成 Pod Project,组装 sandbox 中一切 Pod 的 path、build setting、源文件引证、静态库文件、资源文件等
  create_and_save_projects(pod_targets_to_generate, aggregate_targets_to_generate,
                cache_analysis_result.build_configurations, cache_analysis_result.project_object_version)
  # SandboxDirCleaner 用于整理增量 pod 装置中的无用 headers、target support files 目录
  SandboxDirCleaner.new(sandbox, pod_targets, aggregate_targets).clean!
  # 更新装置后的 cache 结果到目录 `Pods/.project_cache` 下
  update_project_cache(cache_analysis_result, target_installation_results)
end

将之前版别裁定的一切组件经过 project 文件的方法安排起来,并对 project 中做一些用户指定的装备。

6. 写入依靠(write lockfiles)

def write_lockfiles
 @lockfile = generate_lockfile
 UI.message "- Writing Lockfile in #{UI.path config.lockfile_path}" do
  # No need to invoke Sandbox#update_changed_file here since this logic already handles checking if the
  # contents of the file are the same.
  @lockfile.write_to_disk(config.lockfile_path)
 end
 UI.message "- Writing Manifest in #{UI.path sandbox.manifest_path}" do
  # No need to invoke Sandbox#update_changed_file here since this logic already handles checking if the
  # contents of the file are the same.
  @lockfile.write_to_disk(sandbox.manifest_path)
 end
end

将依靠更新写入 Podfile.lock 与 Manifest.lock

7. 完毕回调(perform post install action)

def perform_post_install_actions
 # 调用 HooksManager 履行每个插件的 post_install 办法
 run_plugins_post_install_hooks
 # 打印过期 pod target 正告
 warn_for_deprecations
 # 假如 pod 装备了 script phases 脚本,会自动输出一条提示音讯
 warn_for_installed_script_phases
 # 正告移除的 master specs repo 的 specs
 warn_for_removing_git_master_specs_repo
 # 输出完毕信息 `Pod installation complete!`
 print_post_install_message
end

终究的收尾作业,进行 post install action 的 hook 履行以及一些 warning 打印。

CocoaPods + Plugins

早在 2013 年,CocoaPods 就增加了对插件的支撑,以增加不符合依靠办理和生态体系增长为首要目标的功用。CocoaPods Plugins 能够:在 install 前后增加 hook、增加新指令到 pod、以及运用 Ruby 动态性做任何事。下面介绍一下常见的插件:

  • cocoapods-binary:一个比较早期的二进制插件库,是诸多二进制计划的创意来历
  • cocoapods-repo-update:自动化 pod repo update
  • cocoapods-integrate-flutter:将 flutter 与现有 iOS 运用程序集成
  • cocoapods-uploader:上传文件/目录到远程库房

ps:许多插件或许许久未保护,读者运用需自行斟酌。

不太常见概念

CocoaPods 的装备内容简直包含了 Xcode Build 的方方面面,因而存在许多不太常见的概念,在此做一个链接聚合以供参阅。

  • Clang Module / module_map / umbrella header:Clang Module 是 Clang 16.0.0 中引进的概念,用以处理 #include / #import 头文件引进导致的相关问题;module_map 是用以描绘 clang module 与 header 的联系;umbrella header 则是 module_map 中的语法标准,表明指定目录中的头文件都应包含在模块中。

Modules

Clang Module

LLVM 中的 Module

  • Hmap / Xcode Header / CocoaPods Headers

Header Map 是一组头文件信息映射表,用 .hmap 后缀表明,全体结构以 Key-Value 方法存储;Key为头文件称号、Value 为 头文件物理地址。

Xcode Phases – Header 在构建装备中分为 public、private 与 project ,用以与 target 相关;其间 public 、private 就复制到终究产品的 header 和 PrivateHeaders 中,而 project 头文件不对外运用,则不会放到终究产品。

一款能够让大型iOS工程编译速度提升50%的东西

What are build phases?

  • Xcconfig:

一种装备文件,用以对构建设置进行声明与办理,比方区分不同的开发环境等。

Xcode Build Configuration Files

  • On demand resource:WWDC 2015 引进的概念,对资源文件的按需加载。

Introducing On Demand Resources

[1] Cocoapods.org

[2] Xcode Workspace with multiple projects

[3] 深入了解 CocoaPods

[4] 体系了解 iOS 库与结构

[5] Cocoapods script phases

[6] CocoaPods Podfile 解析原理

[7] Semantic Versioning 2.0.0

[8] 一款能够让大型iOS工程编译速度提升50%的东西

[9] CocoaPods Source 办理机制

[10] 版别办理东西及 Ruby 东西链环境

[11] 全体掌握 CocoaPods 中心组件

[12] 工程功率优化:CocoaPods优化