CocoaPods 都做了什么?

稍有 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)檫@里的 sourceplatformtarget 以及 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 installupdate 之間最大的區(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 項(xiàng)目組件化、平臺(tái)化是技術(shù)公司的共同目標(biāo),越來越多的技術(shù)公司推崇使用pod管理第三方庫(kù)以及私有組件,一方面使項(xiàng)目架構(gòu)...
    swu_luo閱讀 22,296評(píng)論 0 39
  • CocoaPods 是什么? CocoaPods 是一個(gè)負(fù)責(zé)管理 iOS 項(xiàng)目中第三方開源庫(kù)的工具。CocoaPo...
    朝洋閱讀 25,745評(píng)論 3 50
  • 2017.04.19 “上帝”暗箱操控,無人提前知道考驗(yàn)的內(nèi)容和規(guī)則,也無人有棄權(quán)選擇,十名參與者只能在兩者之間,...
    微光之冕閱讀 292評(píng)論 0 0
  • 問題的拋出: 回答一: 今天想對(duì)“變量聲明在循環(huán)體外合適還是循環(huán)體內(nèi)合適?”這個(gè)命題吐槽一番,并且我有兩個(gè)前提:1...
    Viking_Den閱讀 10,694評(píng)論 0 5
  • 體驗(yàn)App:ONE 定位(slogan):文藝生活閱讀應(yīng)用 當(dāng)前版本:3.2.4 類型:閱讀社區(qū) 主要功能:閱讀,...
    象大人閱讀 10,276評(píng)論 2 9