稍有 iOS 開發(fā)經(jīng)驗(yàn)的人應(yīng)該都是用過 CocoaPods,而對(duì)于 CI、CD 有了解的同學(xué)也都知道 Fastlane。而這兩個(gè)在 iOS 開發(fā)中非常便捷的第三方庫(kù)都是使用 Ruby 來編寫的,這是為什么?
先拋開這個(gè)話題不談,我們來看一下 CocoaPods 和 Fastlane 是如何使用的,首先是 CocoaPods,在每一個(gè)工程使用 CocoaPods 的工程中都有一個(gè) Podfile:
source 'https://github.com/CocoaPods/Specs.git'
target 'Demo' do
pod 'Mantle', '~> 1.5.1'
pod 'SDWebImage', '~> 3.7.1'
pod 'BlocksKit', '~> 2.2.5'
pod 'SSKeychain', '~> 1.2.3'
pod 'UMengAnalytics', '~> 3.1.8'
pod 'UMengFeedback', '~> 1.4.2'
pod 'Masonry', '~> 0.5.3'
pod 'AFNetworking', '~> 2.4.1'
pod 'Aspects', '~> 1.4.1'
end
這是一個(gè)使用 Podfile 定義依賴的一個(gè)例子,不過 Podfile 對(duì)約束的描述其實(shí)是這樣的:
source('https://github.com/CocoaPods/Specs.git')
target('Demo') do
pod('Mantle', '~> 1.5.1')
...
end
Ruby 代碼在調(diào)用方法時(shí)可以省略括號(hào)。
Podfile 中對(duì)于約束的描述,其實(shí)都可以看作是對(duì)代碼簡(jiǎn)寫,上面的代碼在解析時(shí)可以當(dāng)做 Ruby 代碼來執(zhí)行。
Fastlane 中的代碼 Fastfile 也是類似的:
lane :beta do
increment_build_number
cocoapods
match
testflight
sh "./customScript.sh"
slack
end
使用描述性的”代碼“編寫腳本,如果沒有接觸或者使用過 Ruby 的人很難相信上面的這些文本是代碼的。
Ruby 概述
在介紹 CocoaPods 的實(shí)現(xiàn)之前,我們需要對(duì) Ruby 的一些特性有一個(gè)簡(jiǎn)單的了解,在向身邊的朋友“傳教”的時(shí)候,我往往都會(huì)用優(yōu)雅這個(gè)詞來形容這門語(yǔ)言(手動(dòng)微笑)。
除了優(yōu)雅之外,Ruby 的語(yǔ)法具有強(qiáng)大的表現(xiàn)力,并且其使用非常靈活,能快速實(shí)現(xiàn)我們的需求,這里簡(jiǎn)單介紹一下 Ruby 中的一些特性。
一切皆對(duì)象
在許多語(yǔ)言,比如 Java 中,數(shù)字與其他的基本類型都不是對(duì)象,而在 Ruby 中所有的元素,包括基本類型都是對(duì)象,同時(shí)也不存在運(yùn)算符的概念,所謂的 1 + 1
,其實(shí)只是 1.+(1)
的語(yǔ)法糖而已。
得益于一切皆對(duì)象的概念,在 Ruby 中,你可以向任意的對(duì)象發(fā)送 methods
消息,在運(yùn)行時(shí)自省,所以筆者在每次忘記方法時(shí),都會(huì)直接用 methods
來“查文檔”:
2.3.1 :003 > 1.methods
=> [:%, :&, :*, :+, :-, :/, :<, :>, :^, :|, :~, :-@, :**, :<=>, :<<, :>>, :<=, :>=, :==, :===, :[], :inspect, :size, :succ, :to_s, :to_f, :div, :divmod, :fdiv, :modulo, :abs, :magnitude, :zero?, :odd?, :even?, :bit_length, :to_int, :to_i, :next, :upto, :chr, :ord, :integer?, :floor, :ceil, :round, :truncate, :downto, :times, :pred, :to_r, :numerator, :denominator, :rationalize, :gcd, :lcm, :gcdlcm, :+@, :eql?, :singleton_method_added, :coerce, :i, :remainder, :real?, :nonzero?, :step, :positive?, :negative?, :quo, :arg, :rectangular, :rect, :polar, :real, :imaginary, :imag, :abs2, :angle, :phase, :conjugate, :conj, :to_c, :between?, :instance_of?, :public_send, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :private_methods, :kind_of?, :instance_variables, :tap, :is_a?, :extend, :define_singleton_method, :to_enum, :enum_for, :=~, :!~, :respond_to?, :freeze, :display, :send, :object_id, :method, :public_method, :singleton_method, :nil?, :hash, :class, :singleton_class, :clone, :dup, :itself, :taint, :tainted?, :untaint, :untrust, :trust, :untrusted?, :methods, :protected_methods, :frozen?, :public_methods, :singleton_methods, :!, :!=, :__send__, :equal?, :instance_eval, :instance_exec, :__id__]
比如在這里向?qū)ο?1
調(diào)用 methods
就會(huì)返回它能響應(yīng)的所有方法。
一切皆對(duì)象不僅減少了語(yǔ)言中類型的不一致,消滅了基本數(shù)據(jù)類型與對(duì)象之間的邊界;這一概念同時(shí)也簡(jiǎn)化了語(yǔ)言中的組成元素,這樣 Ruby 中只有對(duì)象和方法,這兩個(gè)概念,這也降低了我們理解這門語(yǔ)言的復(fù)雜度:
- 使用對(duì)象存儲(chǔ)狀態(tài)
- 對(duì)象之間通過方法通信
block
Ruby 對(duì)函數(shù)式編程范式的支持是通過 block,這里的 block 和 Objective-C 中的 block 有些不同。
首先 Ruby 中的 block 也是一種對(duì)象,所有的 Block 都是 Proc 類的實(shí)例,也就是所有的 block 都是 first-class 的,可以作為參數(shù)傳遞,返回。
def twice(&proc)
2.times { proc.call() } if proc
end
def twice
2.times { yield } if block_given?
end
yield
會(huì)調(diào)用外部傳入的 block,block_given?
用于判斷當(dāng)前方法是否傳入了block
。
在這個(gè)方法調(diào)用時(shí),是這樣的:
twice do
puts "Hello"
end
eval
最后一個(gè)需要介紹的特性就是 eval
了,早在幾十年前的 Lisp 語(yǔ)言就有了 eval
這個(gè)方法,這個(gè)方法會(huì)將字符串當(dāng)做代碼來執(zhí)行,也就是說 eval
模糊了代碼與數(shù)據(jù)之間的邊界。
> eval "1 + 2 * 3"
=> 7
有了 eval
方法,我們就獲得了更加強(qiáng)大的動(dòng)態(tài)能力,在運(yùn)行時(shí),使用字符串來改變控制流程,執(zhí)行代碼;而不需要去手動(dòng)解析輸入、生成語(yǔ)法樹。
手動(dòng)解析 Podfile
在我們對(duì) Ruby 這門語(yǔ)言有了一個(gè)簡(jiǎn)單的了解之后,就可以開始寫一個(gè)簡(jiǎn)易的解析 Podfile 的腳本了。
在這里,我們以一個(gè)非常簡(jiǎn)單的 Podfile 為例,使用 Ruby 腳本解析 Podfile 中指定的依賴:
source 'http://source.git'
platform :ios, '8.0'
target 'Demo' do
pod 'AFNetworking'
pod 'SDWebImage'
pod 'Masonry'
pod "Typeset"
pod 'BlocksKit'
pod 'Mantle'
pod 'IQKeyboardManager'
pod 'IQDropDownTextField'
end
因?yàn)檫@里的 source
、platform
、target
以及 pod
都是方法,所以在這里我們需要構(gòu)建一個(gè)包含上述方法的上下文:
# eval_pod.rb
$hash_value = {}
def source(url)
end
def target(target)
end
def platform(platform, version)
end
def pod(pod)
end
使用一個(gè)全局變量 hash_value
存儲(chǔ) Podfile 中指定的依賴,并且構(gòu)建了一個(gè) Podfile 解析腳本的骨架;我們先不去完善這些方法的實(shí)現(xiàn)細(xì)節(jié),先嘗試一下讀取 Podfile 中的內(nèi)容并執(zhí)行會(huì)不會(huì)有什么問題。
在 eval_pod.rb
文件的最下面加入這幾行代碼:
content = File.read './Podfile'
eval content
p $hash_value
這里讀取了 Podfile 文件中的內(nèi)容,并把其中的內(nèi)容當(dāng)做字符串執(zhí)行,最后打印 hash_value
的值。
$ ruby eval_pod.rb
運(yùn)行這段 Ruby 代碼雖然并沒有什么輸出,但是并沒有報(bào)出任何的錯(cuò)誤,接下來我們就可以完善這些方法了:
def source(url)
$hash_value['source'] = url
end
def target(target)
targets = $hash_value['targets']
targets = [] if targets == nil
targets << target
$hash_value['targets'] = targets
yield if block_given?
end
def platform(platform, version)
end
def pod(pod)
pods = $hash_value['pods']
pods = [] if pods == nil
pods << pod
$hash_value['pods'] = pods
end
在添加了這些方法的實(shí)現(xiàn)之后,再次運(yùn)行腳本就會(huì)得到 Podfile 中的依賴信息了,不過這里的實(shí)現(xiàn)非常簡(jiǎn)單的,很多情況都沒有處理:
$ ruby eval_pod.rb
{"source"=>"http://source.git", "targets"=>["Demo"], "pods"=>["AFNetworking", "SDWebImage", "Masonry", "Typeset", "BlocksKit", "Mantle", "IQKeyboardManager", "IQDropDownTextField"]}
CocoaPods 中對(duì)于 Podfile 的解析與這里的實(shí)現(xiàn)其實(shí)差不多,接下來就進(jìn)入了 CocoaPods 的實(shí)現(xiàn)部分了。
CocoaPods 的實(shí)現(xiàn)
在上面簡(jiǎn)單介紹了 Ruby 的一些語(yǔ)法以及如何解析 Podfile 之后,我們開始深入了解一下 CocoaPods 是如何管理 iOS 項(xiàng)目的依賴,也就是 pod install
到底做了些什么。
Pod install 的過程
pod install
這個(gè)命令到底做了什么?首先,在 CocoaPods 中,所有的命令都會(huì)由 Command
類派發(fā)到將對(duì)應(yīng)的類,而真正執(zhí)行 pod install
的類就是 Install
:
module Pod
class Command
class Install < Command
def run
verify_podfile_exists!
installer = installer_for_config
installer.repo_update = repo_update?(:default => false)
installer.update = false
installer.install!
end
end
end
end
這里面會(huì)從配置類的實(shí)例 config
中獲取一個(gè) Installer
的實(shí)例,然后執(zhí)行 install!
方法,這里的 installer
有一個(gè) update
屬性,而這也就是 pod install
和 update
之間最大的區(qū)別,其中后者會(huì)無視已有的 Podfile.lock 文件,重新對(duì)依賴進(jìn)行分析:
module Pod
class Command
class Update < Command
def run
...
installer = installer_for_config
installer.repo_update = repo_update?(:default => true)
installer.update = true
installer.install!
end
end
end
end
Podfile 的解析
Podfile 中依賴的解析其實(shí)是與我們?cè)谑謩?dòng)解析 Podfile 章節(jié)所介紹的差不多,整個(gè)過程主要都是由 CocoaPods-Core 這個(gè)模塊來完成的,而這個(gè)過程早在 installer_for_config
中就已經(jīng)開始了:
def installer_for_config
Installer.new(config.sandbox, config.podfile, config.lockfile)
end
這個(gè)方法會(huì)從 config.podfile
中取出一個(gè) Podfile
類的實(shí)例:
def podfile
@podfile ||= Podfile.from_file(podfile_path) if podfile_path
end
類方法 Podfile.from_file
就定義在 CocoaPods-Core 這個(gè)庫(kù)中,用于分析 Podfile 中定義的依賴,這個(gè)方法會(huì)根據(jù) Podfile 不同的類型選擇不同的調(diào)用路徑:
Podfile.from_file
`-- Podfile.from_ruby
|-- File.open
`-- eval
from_ruby
類方法就會(huì)像我們?cè)谇懊孀龅慕馕?Podfile 的方法一樣,從文件中讀取數(shù)據(jù),然后使用 eval
直接將文件中的內(nèi)容當(dāng)做 Ruby 代碼來執(zhí)行。
def self.from_ruby(path, contents = nil)
contents ||= File.open(path, 'r:utf-8', &:read)
podfile = Podfile.new(path) do
begin
eval(contents, nil, path.to_s)
rescue Exception => e
message = "Invalid `#{path.basename}` file: #{e.message}"
raise DSLError.new(message, path, e, contents)
end
end
podfile
end
在 Podfile 這個(gè)類的頂部,我們使用 Ruby 的 Mixin
的語(yǔ)法來混入 Podfile 中代碼執(zhí)行所需要的上下文:
include Pod::Podfile::DSL
Podfile 中的所有你見到的方法都是定義在 DSL
這個(gè)模塊下面的:
module Pod
class Podfile
module DSL
def pod(name = nil, *requirements) end
def target(name, options = nil) end
def platform(name, target = nil) end
def inhibit_all_warnings! end
def use_frameworks!(flag = true) end
def source(source) end
...
end
end
end
這里定義了很多 Podfile 中使用的方法,當(dāng)使用 eval
執(zhí)行文件中的代碼時(shí),就會(huì)執(zhí)行這個(gè)模塊里的方法,在這里簡(jiǎn)單看一下其中幾個(gè)方法的實(shí)現(xiàn),比如說 source
方法:
def source(source)
hash_sources = get_hash_value('sources') || []
hash_sources << source
set_hash_value('sources', hash_sources.uniq)
end
該方法會(huì)將新的 source
加入已有的源數(shù)組中,然后更新原有的 sources
對(duì)應(yīng)的值。
稍微復(fù)雜一些的是 target
方法:
def target(name, options = nil)
if options
raise Informative, "Unsupported options `#{options}` for " \
"target `#{name}`."
end
parent = current_target_definition
definition = TargetDefinition.new(name, parent)
self.current_target_definition = definition
yield if block_given?
ensure
self.current_target_definition = parent
end
這個(gè)方法會(huì)創(chuàng)建一個(gè) TargetDefinition
類的實(shí)例,然后將當(dāng)前環(huán)境系的 target_definition
設(shè)置成這個(gè)剛剛創(chuàng)建的實(shí)例。這樣,之后使用 pod
定義的依賴都會(huì)填充到當(dāng)前的 TargetDefinition
中:
def pod(name = nil, *requirements)
unless name
raise StandardError, 'A dependency requires a name.'
end
current_target_definition.store_pod(name, *requirements)
end
當(dāng) pod
方法被調(diào)用時(shí),會(huì)執(zhí)行 store_pod
將依賴存儲(chǔ)到當(dāng)前 target
中的 dependencies
數(shù)組中:
def store_pod(name, *requirements)
return if parse_subspecs(name, requirements)
parse_inhibit_warnings(name, requirements)
parse_configuration_whitelist(name, requirements)
if requirements && !requirements.empty?
pod = { name => requirements }
else
pod = name
end
get_hash_value('dependencies', []) << pod
nil
end
總結(jié)一下,CocoaPods 對(duì) Podfile 的解析與我們?cè)谇懊孀龅氖謩?dòng)解析 Podfile 的原理差不多,構(gòu)建一個(gè)包含一些方法的上下文,然后直接執(zhí)行 eval
方法將文件的內(nèi)容當(dāng)做代碼來執(zhí)行,這樣只要 Podfile 中的數(shù)據(jù)是符合規(guī)范的,那么解析 Podfile 就是非常簡(jiǎn)單容易的。
安裝依賴的過程
Podfile 被解析后的內(nèi)容會(huì)被轉(zhuǎn)化成一個(gè) Podfile
類的實(shí)例,而 Installer
的實(shí)例方法 install!
就會(huì)使用這些信息安裝當(dāng)前工程的依賴,而整個(gè)安裝依賴的過程大約有四個(gè)部分:
- 解析 Podfile 中的依賴
- 下載依賴
- 創(chuàng)建
Pods.xcodeproj
工程 - 集成 workspace
def install!
resolve_dependencies
download_dependencies
generate_pods_project
integrate_user_project
end
在上面的 install
方法調(diào)用的 resolve_dependencies
會(huì)創(chuàng)建一個(gè) Analyzer
類的實(shí)例,在這個(gè)方法中,你會(huì)看到一些非常熟悉的字符串:
def resolve_dependencies
analyzer = create_analyzer
plugin_sources = run_source_provider_hooks
analyzer.sources.insert(0, *plugin_sources)
UI.section 'Updating local specs repositories' do
analyzer.update_repositories
end if repo_update?
UI.section 'Analyzing dependencies' do
analyze(analyzer)
validate_build_configurations
clean_sandbox
end
end
在使用 CocoaPods 中經(jīng)常出現(xiàn)的 Updating local specs repositories
以及 Analyzing dependencies
就是從這里輸出到終端的,該方法不僅負(fù)責(zé)對(duì)本地所有 PodSpec 文件的更新,還會(huì)對(duì)當(dāng)前 Podfile
中的依賴進(jìn)行分析:
def analyze(analyzer = create_analyzer)
analyzer.update = update
@analysis_result = analyzer.analyze
@aggregate_targets = analyzer.result.targets
end
analyzer.analyze
方法最終會(huì)調(diào)用 Resolver
的實(shí)例方法 resolve
:
def resolve
dependencies = podfile.target_definition_list.flat_map do |target|
target.dependencies.each do |dep|
@platforms_by_dependency[dep].push(target.platform).uniq! if target.platform
end
end
@activated = Molinillo::Resolver.new(self, self).resolve(dependencies, locked_dependencies)
specs_by_target
rescue Molinillo::ResolverError => e
handle_resolver_error(e)
end
這里的 Molinillo::Resolver
就是用于解決依賴關(guān)系的類。
解決依賴關(guān)系(Resolve Dependencies)
CocoaPods 為了解決 Podfile 中聲明的依賴關(guān)系,使用了一個(gè)叫做 Milinillo 的依賴關(guān)系解決算法;但是,筆者在 Google 上并沒有找到與這個(gè)算法相關(guān)的其他信息,推測(cè)是 CocoaPods 為了解決 iOS 中的依賴關(guān)系創(chuàng)造的算法。
Milinillo 算法的核心是 回溯(Backtracking) 以及 向前檢查(forward check),整個(gè)過程會(huì)追蹤棧中的兩個(gè)狀態(tài)(依賴和可能性)。
在這里并不想陷入對(duì)這個(gè)算法執(zhí)行過程的分析之中,如果有興趣可以看一下倉(cāng)庫(kù)中的 ARCHITECTURE.md 文件,其中比較詳細(xì)的解釋了 Milinillo 算法的工作原理,并對(duì)其功能執(zhí)行過程有一個(gè)比較詳細(xì)的介紹。
Molinillo::Resolver
方法會(huì)返回一個(gè)依賴圖,其內(nèi)容大概是這樣的:
Molinillo::DependencyGraph:[
Molinillo::DependencyGraph::Vertex:AFNetworking(#<Pod::Specification name="AFNetworking">),
Molinillo::DependencyGraph::Vertex:SDWebImage(#<Pod::Specification name="SDWebImage">),
Molinillo::DependencyGraph::Vertex:Masonry(#<Pod::Specification name="Masonry">),
Molinillo::DependencyGraph::Vertex:Typeset(#<Pod::Specification name="Typeset">),
Molinillo::DependencyGraph::Vertex:CCTabBarController(#<Pod::Specification name="CCTabBarController">),
Molinillo::DependencyGraph::Vertex:BlocksKit(#<Pod::Specification name="BlocksKit">),
Molinillo::DependencyGraph::Vertex:Mantle(#<Pod::Specification name="Mantle">),
...
]
這個(gè)依賴圖是由一個(gè)結(jié)點(diǎn)數(shù)組組成的,在 CocoaPods 拿到了這個(gè)依賴圖之后,會(huì)在 specs_by_target
中按照 Target
將所有的 Specification
分組:
{
#<Pod::Podfile::TargetDefinition label=Pods>=>[],
#<Pod::Podfile::TargetDefinition label=Pods-Demo>=>[
#<Pod::Specification name="AFNetworking">,
#<Pod::Specification name="AFNetworking/NSURLSession">,
#<Pod::Specification name="AFNetworking/Reachability">,
#<Pod::Specification name="AFNetworking/Security">,
#<Pod::Specification name="AFNetworking/Serialization">,
#<Pod::Specification name="AFNetworking/UIKit">,
#<Pod::Specification name="BlocksKit/Core">,
#<Pod::Specification name="BlocksKit/DynamicDelegate">,
#<Pod::Specification name="BlocksKit/MessageUI">,
#<Pod::Specification name="BlocksKit/UIKit">,
#<Pod::Specification name="CCTabBarController">,
#<Pod::Specification name="CategoryCluster">,
...
]
}
而這些 Specification
就包含了當(dāng)前工程依賴的所有第三方框架,其中包含了名字、版本、源等信息,用于依賴的下載。
下載依賴
在依賴關(guān)系解決返回了一系列 Specification
對(duì)象之后,就到了 Pod install 的第二部分,下載依賴:
def install_pod_sources
@installed_specs = []
pods_to_install = sandbox_state.added | sandbox_state.changed
title_options = { :verbose_prefix => '-> '.green }
root_specs.sort_by(&:name).each do |spec|
if pods_to_install.include?(spec.name)
if sandbox_state.changed.include?(spec.name) && sandbox.manifest
previous = sandbox.manifest.version(spec.name)
title = "Installing #{spec.name} #{spec.version} (was #{previous})"
else
title = "Installing #{spec}"
end
UI.titled_section(title.green, title_options) do
install_source_of_pod(spec.name)
end
else
UI.titled_section("Using #{spec}", title_options) do
create_pod_installer(spec.name)
end
end
end
end
在這個(gè)方法中你會(huì)看到更多熟悉的提示,CocoaPods 會(huì)使用沙盒(sandbox)存儲(chǔ)已有依賴的數(shù)據(jù),在更新現(xiàn)有的依賴時(shí),會(huì)根據(jù)依賴的不同狀態(tài)顯示出不同的提示信息:
-> Using AFNetworking (3.1.0)
-> Using AKPickerView (0.2.7)
-> Using BlocksKit (2.2.5) was (2.2.4)
-> Installing MBProgressHUD (1.0.0)
...
雖然這里的提示會(huì)有三種,但是 CocoaPods 只會(huì)根據(jù)不同的狀態(tài)分別調(diào)用兩種方法:
install_source_of_pod
create_pod_installer
create_pod_installer
方法只會(huì)創(chuàng)建一個(gè) PodSourceInstaller
的實(shí)例,然后加入 pod_installers
數(shù)組中,因?yàn)橐蕾嚨陌姹緵]有改變,所以不需要重新下載,而另一個(gè)方法的 install_source_of_pod
的調(diào)用棧非常龐大:
installer.install_source_of_pod
|-- create_pod_installer
| `-- PodSourceInstaller.new
`-- podSourceInstaller.install!
`-- download_source
`-- Downloader.download
`-- Downloader.download_request
`-- Downloader.download_source
|-- Downloader.for_target
| |-- Downloader.class_for_options
| `-- Git/HTTP/Mercurial/Subversion.new
|-- Git/HTTP/Mercurial/Subversion.download
`-- Git/HTTP/Mercurial/Subversion.download!
`-- Git.clone
在調(diào)用棧的末端 Downloader.download_source
中執(zhí)行了另一個(gè) CocoaPods 組件 CocoaPods-Download 中的方法:
def self.download_source(target, params)
FileUtils.rm_rf(target)
downloader = Downloader.for_target(target, params)
downloader.download
target.mkpath
if downloader.options_specific?
params
else
downloader.checkout_options
end
end
方法中調(diào)用的 for_target
根據(jù)不同的源會(huì)創(chuàng)建一個(gè)下載器,因?yàn)橐蕾嚳赡芡ㄟ^不同的協(xié)議或者方式進(jìn)行下載,比如說 Git/HTTP/SVN 等等,組件 CocoaPods-Downloader 就會(huì)根據(jù) Podfile 中依賴的參數(shù)選項(xiàng)使用不同的方法下載依賴。
大部分的依賴都會(huì)被下載到 ~/Library/Caches/CocoaPods/Pods/Release/
這個(gè)文件夾中,然后從這個(gè)這里復(fù)制到項(xiàng)目工程目錄下的 ./Pods
中,這也就完成了整個(gè) CocoaPods 的下載流程。
生成 Pods.xcodeproj
CocoaPods 通過組件 CocoaPods-Downloader 已經(jīng)成功將所有的依賴下載到了當(dāng)前工程中,這里會(huì)將所有的依賴打包到 Pods.xcodeproj
中:
def generate_pods_project(generator = create_generator)
UI.section 'Generating Pods project' do
generator.generate!
@pods_project = generator.project
run_podfile_post_install_hooks
generator.write
generator.share_development_pod_schemes
write_lockfiles
end
end
generate_pods_project
中會(huì)執(zhí)行 PodsProjectGenerator
的實(shí)例方法 generate!
:
def generate!
prepare
install_file_references
install_libraries
set_target_dependencies
end
這個(gè)方法做了幾件小事:
- 生成
Pods.xcodeproj
工程 - 將依賴中的文件加入工程
- 將依賴中的 Library 加入工程
- 設(shè)置目標(biāo)依賴(Target Dependencies)
這幾件事情都離不開 CocoaPods 的另外一個(gè)組件 Xcodeproj,這是一個(gè)可以操作一個(gè) Xcode 工程中的 Group 以及文件的組件,我們都知道對(duì) Xcode 工程的修改大多數(shù)情況下都是對(duì)一個(gè)名叫 project.pbxproj
的文件進(jìn)行修改,而 Xcodeproj 這個(gè)組件就是 CocoaPods 團(tuán)隊(duì)開發(fā)的用于操作這個(gè)文件的第三方庫(kù)。
生成 workspace
最后的這一部分與生成 Pods.xcodeproj
的過程有一些相似,這里使用的類是 UserProjectIntegrator
,調(diào)用方法 integrate!
時(shí),就會(huì)開始集成工程所需要的 Target:
def integrate!
create_workspace
integrate_user_targets
warn_about_xcconfig_overrides
save_projects
end
對(duì)于這一部分的代碼,也不是很想展開來細(xì)談,簡(jiǎn)單介紹一下這里的代碼都做了什么,首先會(huì)通過 Xcodeproj::Workspace
創(chuàng)建一個(gè) workspace,之后會(huì)獲取所有要集成的 Target 實(shí)例,調(diào)用它們的 integrate!
方法:
def integrate!
UI.section(integration_message) do
XCConfigIntegrator.integrate(target, native_targets)
add_pods_library
add_embed_frameworks_script_phase
remove_embed_frameworks_script_phase_from_embedded_targets
add_copy_resources_script_phase
add_check_manifest_lock_script_phase
end
end
方法將每一個(gè) Target 加入到了工程,使用 Xcodeproj 修改 Copy Resource Script Phrase
等設(shè)置,保存 project.pbxproj
,整個(gè) Pod install 的過程就結(jié)束了。
總結(jié)
最后想說的是 pod install 和 pod update 區(qū)別還是比較大的,每次在執(zhí)行 pod install 或者 update 時(shí)最后都會(huì)生成或者修改 Podfile.lock
文件,其中前者并不會(huì)修改 Podfile.lock
中顯示指定的版本,而后者會(huì)會(huì)無視該文件的內(nèi)容,嘗試將所有的 pod 更新到最新版。
CocoaPods 工程的代碼雖然非常多,不過代碼的邏輯非常清晰,整個(gè)管理并下載依賴的過程非常符合直覺以及邏輯。
其它
Github Repo:iOS-Source-Code-Analyze
Follow: Draveness · GitHub
Source: http://draveness.me/cocoapods