CocoaPods 都做了什么把夸?

稍有 iOS 開發(fā)經(jīng)驗的人應(yīng)該都是用過 CocoaPods灵嫌,而對于 CI、CD 有了解的同學(xué)也都知道 Fastlane氛驮。而這兩個在 iOS 開發(fā)中非常便捷的第三方庫都是使用 Ruby 來編寫的腕柜,這是為什么?

先拋開這個話題不談矫废,我們來看一下 CocoaPods 和 Fastlane 是如何使用的盏缤,首先是 CocoaPods,在每一個工程使用 CocoaPods 的工程中都有一個 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

這是一個使用 Podfile 定義依賴的一個例子蓖扑,不過 Podfile 對約束的描述其實是這樣的:

source('https://github.com/CocoaPods/Specs.git')

target('Demo') do
    pod('Mantle', '~> 1.5.1')
    ...
end

Ruby 代碼在調(diào)用方法時可以省略括號唉铜。

Podfile 中對于約束的描述,其實都可以看作是對代碼簡寫律杠,上面的代碼在解析時可以當(dāng)做 Ruby 代碼來執(zhí)行潭流。

Fastlane 中的代碼 Fastfile 也是類似的:

lane :beta do
  increment_build_number
  cocoapods
  match
  testflight
  sh "./customScript.sh"
  slack
end

使用描述性的”代碼“編寫腳本竞惋,如果沒有接觸或者使用過 Ruby 的人很難相信上面的這些文本是代碼的。

Ruby 概述

在介紹 CocoaPods 的實現(xiàn)之前灰嫉,我們需要對 Ruby 的一些特性有一個簡單的了解拆宛,在向身邊的朋友“傳教”的時候,我往往都會用優(yōu)雅這個詞來形容這門語言(手動微笑)熬甫。

除了優(yōu)雅之外胰挑,Ruby 的語法具有強大的表現(xiàn)力,并且其使用非常靈活椿肩,能快速實現(xiàn)我們的需求瞻颂,這里簡單介紹一下 Ruby 中的一些特性。

一切皆對象

在許多語言郑象,比如 Java 中贡这,數(shù)字與其他的基本類型都不是對象,而在 Ruby 中所有的元素厂榛,包括基本類型都是對象盖矫,同時也不存在運算符的概念,所謂的 1 + 1击奶,其實只是 1.+(1) 的語法糖而已辈双。

得益于一切皆對象的概念,在 Ruby 中柜砾,你可以向任意的對象發(fā)送 methods 消息湃望,在運行時自省,所以筆者在每次忘記方法時痰驱,都會直接用 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 就會返回它能響應(yīng)的所有方法证芭。

一切皆對象不僅減少了語言中類型的不一致,消滅了基本數(shù)據(jù)類型與對象之間的邊界担映;這一概念同時也簡化了語言中的組成元素废士,這樣 Ruby 中只有對象和方法,這兩個概念蝇完,這也降低了我們理解這門語言的復(fù)雜度:

  • 使用對象存儲狀態(tài)
  • 對象之間通過方法通信

block

Ruby 對函數(shù)式編程范式的支持是通過 block官硝,這里的 block 和 Objective-C 中的 block 有些不同。

首先 Ruby 中的 block 也是一種對象短蜕,所有的 Block 都是 Proc 類的實例氢架,也就是所有的 block 都是 first-class 的,可以作為參數(shù)傳遞忿危,返回达箍。

def twice(&proc)
    2.times { proc.call() } if proc
end

def twice
    2.times { yield } if block_given?
end

yield 會調(diào)用外部傳入的 block,block_given? 用于判斷當(dāng)前方法是否傳入了 block铺厨。

在這個方法調(diào)用時缎玫,是這樣的:

twice do 
    puts "Hello"
end

eval

最后一個需要介紹的特性就是 eval 了硬纤,早在幾十年前的 Lisp 語言就有了 eval 這個方法,這個方法會將字符串當(dāng)做代碼來執(zhí)行赃磨,也就是說 eval 模糊了代碼與數(shù)據(jù)之間的邊界筝家。

> eval "1 + 2 * 3"
 => 7

有了 eval 方法,我們就獲得了更加強大的動態(tài)能力邻辉,在運行時溪王,使用字符串來改變控制流程,執(zhí)行代碼值骇;而不需要去手動解析輸入莹菱、生成語法樹。

手動解析 Podfile

在我們對 Ruby 這門語言有了一個簡單的了解之后吱瘩,就可以開始寫一個簡易的解析 Podfile 的腳本了道伟。

在這里,我們以一個非常簡單的 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

因為這里的 source蜜徽、platformtarget 以及 pod 都是方法票摇,所以在這里我們需要構(gòu)建一個包含上述方法的上下文:

# eval_pod.rb
$hash_value = {}

def source(url)
end

def target(target)
end

def platform(platform, version)
end

def pod(pod)
end

使用一個全局變量 hash_value 存儲 Podfile 中指定的依賴拘鞋,并且構(gòu)建了一個 Podfile 解析腳本的骨架;我們先不去完善這些方法的實現(xiàn)細節(jié)矢门,先嘗試一下讀取 Podfile 中的內(nèi)容并執(zhí)行會不會有什么問題盆色。

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

運行這段 Ruby 代碼雖然并沒有什么輸出缕允,但是并沒有報出任何的錯誤峡扩,接下來我們就可以完善這些方法了:

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

在添加了這些方法的實現(xiàn)之后,再次運行腳本就會得到 Podfile 中的依賴信息了障本,不過這里的實現(xiàn)非常簡單的教届,很多情況都沒有處理:

$ ruby eval_pod.rb
{"source"=>"http://source.git", "targets"=>["Demo"], "pods"=>["AFNetworking", "SDWebImage", "Masonry", "Typeset", "BlocksKit", "Mantle", "IQKeyboardManager", "IQDropDownTextField"]}

CocoaPods 中對于 Podfile 的解析與這里的實現(xiàn)其實差不多,接下來就進入了 CocoaPods 的實現(xiàn)部分了驾霜。

CocoaPods 的實現(xiàn)

在上面簡單介紹了 Ruby 的一些語法以及如何解析 Podfile 之后案训,我們開始深入了解一下 CocoaPods 是如何管理 iOS 項目的依賴,也就是 pod install 到底做了些什么粪糙。

Pod install 的過程

pod install 這個命令到底做了什么强霎?首先,在 CocoaPods 中,所有的命令都會由 Command 類派發(fā)到將對應(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

這里面會從配置類的實例 config 中獲取一個 Installer 的實例妓忍,然后執(zhí)行 install! 方法漫贞,這里的 installer 有一個 update 屬性吁断,而這也就是 pod installupdate 之間最大的區(qū)別擦俐,其中后者會無視已有的 Podfile.lock 文件佣蓉,重新對依賴進行分析

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 中依賴的解析其實是與我們在手動解析 Podfile 章節(jié)所介紹的差不多酸舍,整個過程主要都是由 CocoaPods-Core 這個模塊來完成的拉馋,而這個過程早在 installer_for_config 中就已經(jīng)開始了:

def installer_for_config
  Installer.new(config.sandbox, config.podfile, config.lockfile)
end

這個方法會從 config.podfile 中取出一個 Podfile 類的實例:

def podfile
  @podfile ||= Podfile.from_file(podfile_path) if podfile_path
end

類方法 Podfile.from_file 就定義在 CocoaPods-Core 這個庫中榨为,用于分析 Podfile 中定義的依賴,這個方法會根據(jù) Podfile 不同的類型選擇不同的調(diào)用路徑:

Podfile.from_file
`-- Podfile.from_ruby
    |-- File.open
    `-- eval

from_ruby 類方法就會像我們在前面做的解析 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 這個類的頂部蔓腐,我們使用 Ruby 的 Mixin 的語法來混入 Podfile 中代碼執(zhí)行所需要的上下文:

include Pod::Podfile::DSL

Podfile 中的所有你見到的方法都是定義在 DSL 這個模塊下面的:

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í)行文件中的代碼時,就會執(zhí)行這個模塊里的方法合住,在這里簡單看一下其中幾個方法的實現(xiàn)绰精,比如說 source 方法:

def source(source)
  hash_sources = get_hash_value('sources') || []
  hash_sources << source
  set_hash_value('sources', hash_sources.uniq)
end

該方法會將新的 source 加入已有的源數(shù)組中,然后更新原有的 sources 對應(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

這個方法會創(chuàng)建一個 TargetDefinition 類的實例笨使,然后將當(dāng)前環(huán)境系的 target_definition 設(shè)置成這個剛剛創(chuàng)建的實例。這樣僚害,之后使用 pod 定義的依賴都會填充到當(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)用時硫椰,會執(zhí)行 store_pod 將依賴存儲到當(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 對 Podfile 的解析與我們在前面做的手動解析 Podfile 的原理差不多萨蚕,構(gòu)建一個包含一些方法的上下文靶草,然后直接執(zhí)行 eval 方法將文件的內(nèi)容當(dāng)做代碼來執(zhí)行,這樣只要 Podfile 中的數(shù)據(jù)是符合規(guī)范的岳遥,那么解析 Podfile 就是非常簡單容易的奕翔。

安裝依賴的過程

Podfile 被解析后的內(nèi)容會被轉(zhuǎn)化成一個 Podfile 類的實例,而 Installer 的實例方法 install! 就會使用這些信息安裝當(dāng)前工程的依賴浩蓉,而整個安裝依賴的過程大約有四個部分:

  • 解析 Podfile 中的依賴
  • 下載依賴
  • 創(chuàng)建 Pods.xcodeproj 工程
  • 集成 workspace
def install!
  resolve_dependencies
  download_dependencies
  generate_pods_project
  integrate_user_project
end

在上面的 install 方法調(diào)用的 resolve_dependencies 會創(chuàng)建一個 Analyzer 類的實例派继,在這個方法中,你會看到一些非常熟悉的字符串:

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 就是從這里輸出到終端的捻艳,該方法不僅負責(zé)對本地所有 PodSpec 文件的更新驾窟,還會對當(dāng)前 Podfile 中的依賴進行分析:

def analyze(analyzer = create_analyzer)
  analyzer.update = update
  @analysis_result = analyzer.analyze
  @aggregate_targets = analyzer.result.targets
end

analyzer.analyze 方法最終會調(diào)用 Resolver 的實例方法 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)系认轨,使用了一個叫做 Milinillo 的依賴關(guān)系解決算法绅络;但是,筆者在 Google 上并沒有找到與這個算法相關(guān)的其他信息,推測是 CocoaPods 為了解決 iOS 中的依賴關(guān)系創(chuàng)造的算法恩急。

Milinillo 算法的核心是 回溯(Backtracking) 以及 向前檢查(forward check)节视,整個過程會追蹤棧中的兩個狀態(tài)(依賴和可能性)。

在這里并不想陷入對這個算法執(zhí)行過程的分析之中假栓,如果有興趣可以看一下倉庫中的 ARCHITECTURE.md 文件寻行,其中比較詳細的解釋了 Milinillo 算法的工作原理,并對其功能執(zhí)行過程有一個比較詳細的介紹匾荆。

Molinillo::Resolver 方法會返回一個依賴圖拌蜘,其內(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">),
    ...
]

這個依賴圖是由一個結(jié)點數(shù)組組成的,在 CocoaPods 拿到了這個依賴圖之后牙丽,會在 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 對象之后构罗,就到了 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

在這個方法中你會看到更多熟悉的提示,CocoaPods 會使用沙盒(sandbox)存儲已有依賴的數(shù)據(jù)遂唧,在更新現(xiàn)有的依賴時芙代,會根據(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)
...

雖然這里的提示會有三種,但是 CocoaPods 只會根據(jù)不同的狀態(tài)分別調(diào)用兩種方法:

  • install_source_of_pod
  • create_pod_installer

create_pod_installer 方法只會創(chuàng)建一個 PodSourceInstaller 的實例盖彭,然后加入 pod_installers 數(shù)組中纹烹,因為依賴的版本沒有改變,所以不需要重新下載召边,而另一個方法的 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í)行了另一個 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ù)不同的源會創(chuàng)建一個下載器铺呵,因為依賴可能通過不同的協(xié)議或者方式進行下載,比如說 Git/HTTP/SVN 等等隧熙,組件 CocoaPods-Downloader 就會根據(jù) Podfile 中依賴的參數(shù)選項使用不同的方法下載依賴片挂。

大部分的依賴都會被下載到 ~/Library/Caches/CocoaPods/Pods/Release/ 這個文件夾中,然后從這個這里復(fù)制到項目工程目錄下的 ./Pods 中贱鼻,這也就完成了整個 CocoaPods 的下載流程宴卖。

生成 Pods.xcodeproj

CocoaPods 通過組件 CocoaPods-Downloader 已經(jīng)成功將所有的依賴下載到了當(dāng)前工程中滋将,這里會將所有的依賴打包到 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 中會執(zhí)行 PodsProjectGenerator 的實例方法 generate!

def generate!
  prepare
  install_file_references
  install_libraries
  set_target_dependencies
end

這個方法做了幾件小事:

  • 生成 Pods.xcodeproj 工程
  • 將依賴中的文件加入工程
  • 將依賴中的 Library 加入工程
  • 設(shè)置目標(biāo)依賴(Target Dependencies)

這幾件事情都離不開 CocoaPods 的另外一個組件 Xcodeproj邻悬,這是一個可以操作一個 Xcode 工程中的 Group 以及文件的組件,我們都知道對 Xcode 工程的修改大多數(shù)情況下都是對一個名叫 project.pbxproj 的文件進行修改随闽,而 Xcodeproj 這個組件就是 CocoaPods 團隊開發(fā)的用于操作這個文件的第三方庫父丰。

生成 workspace

最后的這一部分與生成 Pods.xcodeproj 的過程有一些相似,這里使用的類是 UserProjectIntegrator,調(diào)用方法 integrate! 時蛾扇,就會開始集成工程所需要的 Target:

def integrate!
  create_workspace
  integrate_user_targets
  warn_about_xcconfig_overrides
  save_projects
end

對于這一部分的代碼攘烛,也不是很想展開來細談,簡單介紹一下這里的代碼都做了什么镀首,首先會通過 Xcodeproj::Workspace 創(chuàng)建一個 workspace坟漱,之后會獲取所有要集成的 Target 實例,調(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

方法將每一個 Target 加入到了工程更哄,使用 Xcodeproj 修改 Copy Resource Script Phrase 等設(shè)置芋齿,保存 project.pbxproj,整個 Pod install 的過程就結(jié)束了成翩。

總結(jié)

最后想說的是 pod install 和 pod update 區(qū)別還是比較大的觅捆,每次在執(zhí)行 pod install 或者 update 時最后都會生成或者修改 Podfile.lock 文件,其中前者并不會修改 Podfile.lock顯示指定的版本麻敌,而后者會會無視該文件的內(nèi)容栅炒,嘗試將所有的 pod 更新到最新版。

CocoaPods 工程的代碼雖然非常多术羔,不過代碼的邏輯非常清晰赢赊,整個管理并下載依賴的過程非常符合直覺以及邏輯。

其它

Github Repo:iOS-Source-Code-Analyze

Follow: Draveness · GitHub

Source: http://draveness.me/cocoapods

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末级历,一起剝皮案震驚了整個濱河市域携,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鱼喉,老刑警劉巖秀鞭,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異扛禽,居然都是意外死亡锋边,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門编曼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來豆巨,“玉大人,你說我怎么就攤上這事掐场⊥樱” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵熊户,是天一觀的道長萍膛。 經(jīng)常有香客問我,道長嚷堡,這世上最難降的妖魔是什么蝗罗? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上串塑,老公的妹妹穿的比我還像新娘沼琉。我一直安慰自己,他們只是感情好桩匪,可當(dāng)我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布打瘪。 她就那樣靜靜地躺著,像睡著了一般傻昙。 火紅的嫁衣襯著肌膚如雪瑟慈。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天屋匕,我揣著相機與錄音葛碧,去河邊找鬼。 笑死过吻,一個胖子當(dāng)著我的面吹牛进泼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播纤虽,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼乳绕,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了逼纸?” 一聲冷哼從身側(cè)響起洋措,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎杰刽,沒想到半個月后菠发,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡贺嫂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年滓鸠,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片第喳。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡糜俗,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出曲饱,到底是詐尸還是另有隱情悠抹,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布扩淀,位于F島的核電站楔敌,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏引矩。R本人自食惡果不足惜梁丘,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一侵浸、第九天 我趴在偏房一處隱蔽的房頂上張望旺韭。 院中可真熱鬧氛谜,春花似錦、人聲如沸区端。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽织盼。三九已至杨何,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間沥邻,已是汗流浹背危虱。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留唐全,地道東北人埃跷。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像邮利,于是被迫代替她去往敵國和親弥雹。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,033評論 2 355

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

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