2. 總體把握 CocoaPods 核心組件

image

CocoaPods歷險記這個專題是 Edmond冬瓜共同撰寫,對於 iOS / macOS 工程中版本管理工具 CocoaPods 的實現細節、原理、源碼、實踐與經驗的分享記錄,旨在幫助你們可以更加了解這個依賴管理工具,而不只侷限於 pod installpod update

本文知識目錄

CocoaPods-Perview

引子

在上文 版本管理工具及 Ruby 工具鏈環境 中,咱們聊到如何統一管理團隊小夥伴的 CocoaPods 生產環境及使用到的 Ruby 工具鏈。今天讓咱們將目光轉到 CocoaPods 身上,一塊兒來聊聊它的主要構成,以及各個組件在整個 Pods 工做流的關係。html

爲了總體把握 CocoaPods 這個項目,建議你們去入門一下 Ruby 這門腳本語言。另外本文基於 CocoaPods 1.9.2 版本。

CocoaPods 的核心組件

做爲包管理工具,CocoaPods 隨着 Apple 生態的蓬勃發展也在不斷迭代和進化,而且各部分核心功能也都演化出相對獨立的組件。這些功能獨立的組件,均拆分出一個個獨立的 Gem 包,而 CocoaPods 則是這些組件的「集大成者」。git

CocoaPods 依賴總覽

咱們知道在 Pod 管理的項目中,Podfile 文件裏描述了它所依賴的 dependencies,相似的 Gem 的依賴能夠在 Gemfile 中查看。那 CocoaPods 的 Gemfile 有哪些依賴呢?github

SKIP_UNRELEASED_VERSIONS = false
# ...

source 'https://rubygems.org'
gemspec
gem 'json', :git => 'https://github.com/segiddins/json.git', :branch => 'seg-1.7.7-ruby-2.2'

group :development do
  cp_gem 'claide',                'CLAide'
  cp_gem 'cocoapods-core',        'Core', '1-9-stable'
  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-stats',       'cocoapods-stats'
  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'
  # ...
  # Integration tests
  gem 'diffy'
  gem 'clintegracon'
  # Code Quality
  gem 'inch_by_inch'
  gem 'rubocop'
  gem 'danger'
end

group :debugging do
  gem 'cocoapods_debug'

  gem 'rb-fsevent'
  gem 'kicker'
  gem 'awesome_print'
  gem 'ruby-prof', :platforms => [:ruby]
end

上面的 Gemfile 中咱們看到不少經過 cp_gem 裝載的 Gem 庫,其方法以下:算法

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

它是用於方便開發和調試,當 **SKIP_UNRELEASED_VERSIONS**false && pathtrue 時會使用與本地的 CocoaPods 項目同級目錄下的 git 倉庫,不然會使用對應的項目直接經過 Gem 加載。shell

經過簡單的目錄分割和 Gemfile 管理,就實現了最基本又最直觀的熱插拔,對組件開發十分友好。因此你只要將多個倉庫以下圖方式排列,便可實現跨倉庫組件開發:json

$ ls -l
lrwxr-xr-x  1 gua  staff    31 Jul 30 21:34 CocoaPods
lrwxr-xr-x  1 gua  staff    26 Jul 31 13:27 Core 
lrwxr-xr-x  1 gua  staff    31 Jul 31 10:14 Molinillo 
lrwxr-xr-x  1 gua  staff    31 Aug 15 11:32 Xcodeproj 
lrwxr-xr-x  1 gua  staff    42 Jul 31 10:14 cocoapods-downloader

組件構成和對應職責

經過上面對於 Gemfile 的簡單分析,能夠看出 CocoaPods 不只僅是一個倉庫那麼簡單,它做爲一個三方庫版本管理工具,對自身組件的管理和組件化也是十分講究的。咱們繼續來看這份 Gemfile 中的核心開發組件:swift

00-Core Components

CLAide

The CLAide gem is a simple command line parser, which provides an API that allows you to quickly create a full featured command-line interface.

CLAide 雖然是一個簡單的命令行解釋器,但它提供了功能齊全的命令行界面和 API。它不只負責解析咱們使用到的 Pods 命令,如:pod install, pod update 等,還可用於封裝經常使用的一些腳本,將其打包成簡單的命令行小工具。xcode

PS: 所謂命令行解釋器就是從標準輸入或者文件中讀取命令並執行的程序。詳見 Wiki

cocoapods-core

The CocoaPods-Core gem provides support to work with the models of CocoaPods, for example the Podspecs or the Podfile.

CocoaPods-Core 用於 CocoaPods 中模板文件的解析,包括 Podfile.podspec,以及全部的 .lock 文件中特殊的 YAML 文件。緩存

cocoapods-downloader

The Cocoapods-downloader gem is a small library that provides downloaders for various source control types (HTTP/SVN/Git/Mercurial). It can deal with tags, commits, revisions, branches, extracting files from zips and almost anything these source control system would use.

Cocoapods-Downloader 是用於下載源碼的小工具,它支持各類類型的版本管理工具,包括 HTTP / SVN / Git / Mercurial。它能夠提供 tagscommitesrevisionsbranches 以及 zips 文件的下載和解壓縮操做。ruby

Molinillo

The Molinillo gem is a generic dependency resolution algorithm, used in CocoaPods, Bundler and RubyGems.

Molinillo 是 CocoaPods 對於依賴仲裁算法的封裝,它是一個具備前向檢察的回溯算法。不只在 Pods 中,BundlerRubyGems 也是使用的這一套仲裁算法。

Xcodeproj

The Xcodeproj gem lets you create and modify Xcode projects from Ruby. Script boring management tasks or build Xcode-friendly libraries. Also includes support for Xcode workspaces (.xcworkspace) and configuration files (.xcconfig).

Xcodeproj 可經過 Ruby 來操做 Xcode 項目的建立和編輯等。可友好的支持 Xcode 項目的腳本管理和 libraries 構建,以及 Xcode 工做空間 (.xcworkspace)  和配置文件 .xcconfig 的管理。

cocoapods-plugins

CocoaPods plugin which shows info about available CocoaPods plugins or helps you get started developing a new plugin. Yeah, it's very meta.

cocoapods-plugins 插件管理功能,其中有 pod plugin 全套命令,支持對於 CocoaPods 插件的列表一覽(list)、搜索(search)、建立(create)功能。

固然,上面還有不少組件這裏就不一一介紹了。經過查看 Gemfile 能夠看出 Pod 對於組件的拆分粒度是比較細微的,經過對各類組件的組合達到如今的完整版本。這些組件中,筆者的瞭解也十分有限,不過咱們會在以後的一系列文章來逐一介紹學習。

CocoaPods 初探

接下來,結合 pod install 安裝流程來展現各個組件在 Pods 工做流中的上下游關係。

命令入口

每當咱們輸入 pod xxx 命令時,系統會首先調用 pod 命令。全部的命令都是在 /bin 目錄下存放的腳本,固然 Ruby 環境的也不例外。咱們能夠經過 which pod 來查看命令所在位置:

$ which pod
/Users/edmond/.rvm/gems/ruby-2.6.1/bin/pod
這裏的顯示路徑不是 /usr/local/bin/pod 的緣由是由於使用 RVM 進行版本控制的。

咱們經過 cat 命令來查看一下這個入口腳本執行了什麼

$ cat /Users/edmond/.rvm/gems/ruby-2.6.1/bin/pod

輸出以下:

#!/usr/bin/env ruby_executable_hooks

require 'rubygems'
version = ">= 0.a"

str = ARGV.first
if str
  str = str.b[/\A_(.*)_\z/, 1]
  if str and Gem::Version.correct?(str)
    version = str
    ARGV.shift
  end
end

if Gem.respond_to?(:activate_bin_path)
    load Gem.activate_bin_path('cocoapods', 'pod', version)
else
    gem "cocoapods", version
    load Gem.bin_path("cocoapods", "pod", version)
end

程序 CocoaPods 是做爲 Gem 被安裝的,此腳本用於喚起 CocoaPods。邏輯比較簡單,就是一個單純的命令轉發。Gem.activate_bin_pathGem.bin_path 用於找到 CocoaPods 的安裝目錄 cocoapods/bin,最終加載該目錄下的 /pod 文件:

#!/usr/bin/env ruby
# ... 忽略一些對於編碼處理的代碼

require 'cocoapods'

# 這裏手動輸出一下調用棧,來關注一下
puts caller

# 若是環境配置中指定了 ruby-prof 配置文件,會對執行命令過程進行性能監控
if profile_filename = ENV['COCOAPODS_PROFILE']
  require 'ruby-prof'
  # 依據配置文件類型加載不一樣的 reporter 解析器
  # ...
  File.open(profile_filename, 'w') do |io|
    reporter.new(RubyProf.profile { Pod::Command.run(ARGV) }).print(io)
  end
else
  Pod::Command.run(ARGV)
end

一塊兒來查看一下 pod 命令的輸出結果:

$ pod
/Users/edmond/.rvm/gems/ruby-2.6.1/bin/pod:24:in `load'
/Users/edmond/.rvm/gems/ruby-2.6.1/bin/pod:24:in `<main>'
/Users/edmond/.rvm/gems/ruby-2.6.1/bin/ruby_executable_hooks:24:in `eval'
/Users/edmond/.rvm/gems/ruby-2.6.1/bin/ruby_executable_hooks:24:in `<main>'

ruby_executable_hooks 經過 bin 目錄下的 pod 入口喚醒,再經過 eval 的手段調起咱們須要的 CocoaPods 工程。這是 RVM 的自身行爲,它利用了 executable-hook 來注入 Gems 插件來定製擴展。

PS:大多數動態語言都支持 eval 這一神奇的函數。打  Lisp 開始就支持了,它經過接受一個字符串類型做爲參數,將其解析成語句並混合在當前做用域內運行。詳細能夠參考這篇 文章

在入口的最後部分,經過調用 Pod::Command.run(ARGV),實例化了一個 CLAide::Command 對象,開始咱們的 CLAide 命令解析階段。這裏不對 CLAide 這個命令解析工具作過多的分析,這個是後面系列文章的內容。這裏咱們僅僅須要知道:

每一個 CLAide  命令的執行,最終都會對應到具體 Command Class 的 run 方法。

Pod 命令對應的 run 方法實現以下:

module Pod
  class Command
    class Install < Command
      # ... 
      def run
        # 判斷是否存在 Podfile 文件,若是存在則進行 Podfile 的初始化
        verify_podfile_exists!
        # 從 Config 中獲取一個 Instraller 實例
        installer = installer_for_config
        # 默認是不執行 update
        installer.repo_update = repo_update?(:default => false)
        installer.update = false
        installer.deployment = @deployment
        # install 的真正過程
        installer.install!
      end
    end
  end
end

上述所見的 Command::Install 類對應的命令爲 pod installpod install 過程是依賴於 Podfile 文件的,因此在入口處會作檢測,若是不存在 Podfile 則直接拋出 No 'Podfile' found in the project directory 的異常 警告並結束命令。

執行功能主體

installer 實例組裝完成以後,調用其 install! 方法,這時候才進入了咱們 pod install 命令的主體部分,流程以下圖:

01-pod install.png

對應的實現以下:

def install!
    prepare
    resolve_dependencies
    download_dependencies
    validate_targets
    if installation_options.skip_pods_project_generation?
        show_skip_pods_project_generation_message
    else
        integrate
    end
    write_lockfiles
    perform_post_install_actions
end

def integrate
    generate_pods_project
    if installation_options.integrate_targets?
        integrate_user_project
    else
        UI.section 'Skipping User Project Integration'
    end
end

0x1 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 的裝載插件腳本所有取出,並執行對應的 pre_install hook。

0x2 解決依賴衝突(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

依賴解析過程就是經過 PodfilePodfile.lock 以及沙盒中的 manifest 生成 Analyzer 對象。_Analyzer_ 內部會使用 Molinillo (具體的是 Molinillo::DependencyGraph 圖算法)解析獲得一張依賴關係表。

PS:經過 Analyzer 能獲取到不少依賴信息,例如 Podfile 文件的依賴分析結果,也能夠從 specs_by_target 來查看各個 target 相關的 specs。

另外,須要注意的是 analyze 的過程當中有一個 pre_download 的階段,即在 --verbose 下看到的 Fetching external sources 過程。這個 pre_download 階段不屬於依賴下載過程,而是在當前的依賴分析階段。

PS:該過程主要是解決當咱們在經過 Git 地址引入的 Pod 倉庫的狀況下,系統沒法從默認的 Source 拿到對應的 Spec,須要直接訪問咱們的 Git 地址下載倉庫的 zip 包,並取出對應的 podspec 文件,從而進行對比分析。

0x3 下載依賴文件(download_dependencies)

def download_dependencies
  UI.section 'Downloading dependencies' do
    # 初始化 sandbox 文件訪問器
    create_file_accessors
    # 構造 Pod Source Installer
    install_pod_sources
    # 執行 podfile 定義的 pre install 的 hooks
    run_podfile_pre_install_hooks
    # 根據配置清理 pod sources 信息,主要是清理無用 platform 相關內容
    clean_pod_sources
  end
end

create_file_accessors 中會建立沙盒目錄的文件訪問器,經過構造 FileAccessor 實例來解析沙盒中的各類文件。接着是最重要的 install_pod_sources 過程,它會調用對應 Pod 的 install! 方法進行資源下載。

先來看看 install_pod_sources 方法的實現:

def install_pod_sources
 @installed_specs = []
 # install 的 Pod 只須要這兩種狀態,added 和 changed 狀態的並集
 pods_to_install = sandbox_state.added | sandbox_state.changed
 title_options = { :verbose_prefix => '-> '.green }
 puts "root_specs"
 root_specs.each do |item|
   puts item
 end
 # 將 Podfile 解析後排序處理
 root_specs.sort_by(&:name).each do |spec|
   # 若是是 added 或 changed 狀態的 Pod
   if pods_to_install.include?(spec.name)
     # 若是是 changed 狀態而且 manifest 已經有記錄
     if sandbox_state.changed.include?(spec.name) && sandbox.manifest
       # 版本更新
       current_version = spec.version
       # 被更新版本記錄
       previous_version = sandbox.manifest.version(spec.name)
       # 變更記錄
       has_changed_version = current_version != previous_version
       # 找到第一個包含 spec.name 的 Pod,獲取對應的 Repo,其實就是 find 方法
       current_repo = analysis_result.specs_by_source.detect { |key, values| break key if values.map(&:name).include?(spec.name) }
       # 獲取當前倉庫
       current_repo &&= current_repo.url || current_repo.name
       # 獲取以前該倉庫的信息
       previous_spec_repo = sandbox.manifest.spec_repo(spec.name)
       # 是否倉庫有變更
       has_changed_repo = !previous_spec_repo.nil? && current_repo && (current_repo != previous_spec_repo)

       # 經過 title 輸出上面的詳細變動信息
       title = ...
     else
       # 非 changed 狀態,展現 Installing 這個是常常見的 log
       title = "Installing #{spec}"
     end
     UI.titled_section(title.green, title_options) do
       # 經過 name 拿到對應的 installer,記錄到 @pod_installers 中
       install_source_of_pod(spec.name)
     end
   else
     # 若是沒有 changed 狀況,直接展現 Using,也是常常見到的 log
     UI.titled_section("Using #{spec}", title_options) do
       # # 經過 sandbox, specs 的 platform 信息生成 Installer 實例,記錄到 @pod_installers 中
       create_pod_installer(spec.name)
     end
   end
 end
end

# 經過緩存返回 PodSourceInstaller 實例
def create_pod_installer(pod_name)
    specs_by_platform = specs_for_pod(pod_name)
 
    # 當經過 pod_name 沒法找到對應的 pod_target 或 platform 配置,主動拋出錯誤信息
    if specs_by_platform.empty?
        requiring_targets = pod_targets.select { |pt| pt.recursive_dependent_targets.any? { |dt| dt.pod_name == pod_name } }
        # message = "..."
        raise StandardError, message
    end
    # 經過 sandbox, specs 的 platform 信息生成 Installer 實例
    pod_installer = PodSourceInstaller.new(sandbox, podfile, specs_by_platform, :can_cache => installation_options.clean?)
    pod_installers << pod_installer
    pod_installer
end

# 若是 resolver 聲明一個 Pod 已經安裝或者已經存在,將會將其刪除並從新安裝。若是不存在則直接安裝。
def install_source_of_pod(pod_name)
  pod_installer = create_pod_installer(pod_name)
  pod_installer.install!
  @installed_specs.concat(pod_installer.specs_by_platform.values.flatten.uniq)
end

在方法的開始,root_specs 方法是經過 analysis_result 拿出全部根 spec

def root_specs
  analysis_result.specifications.map(&:root).uniq
end

下面再來看看 pod_installer 中的 install! 方法,主要是經過調用 cocoapods-downloader 組件,將 Pod 對應的 Source 下載到本地。實現以下:

def install!
    download_source unless predownloaded? || local?
    PodSourcePreparer.new(root_spec, root).prepare! if local?
    sandbox.remove_local_podspec(name) unless predownloaded? || local? || external?
end

0x4 驗證 targets (validate_targets)

用來驗證以前流程中的產物 (pod 所生成的 Targets) 的合法性。主要做用就是構造 TargetValidator,並執行 validate! 方法:

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

驗證環節在整個 Install 過程當中僅佔很小的一部分。由於只是驗證部分,是徹底解耦的。

  1. verify_no_duplicate_framework_and_library_names

驗證是否有重名的 framework,若是有衝突會直接拋出 frameworks with conflicting names 異常。

  1. verify_no_static_framework_transitive_dependencies

驗證動態庫中是否有靜態連接庫 (.a 或者 .framework) 依賴,若是存在則會觸發 transitive dependencies that include static binaries... 錯誤。假設存在如下場景:

  1. 組件 A 和組件 B 同時依賴了組件 C,C 爲靜態庫,如 Weibo_SDK
  2. 組件 A 依賴組件 B,而組件 B 的 .podspec 文件中存在如下設置時,組件 B 將被斷定爲存在靜態庫依賴:

    1. podspec 設置了 s.static_framework = true
    2. podspec 以 s.dependency 'xxx_SDK 依賴了靜態庫 xxx_SDK
    3. podspec 以 s.vendored_libraries = 'libxxx.a' 方式內嵌了靜態庫 libxxx

此時若是項目的 Podfile 設置了 use_framework! 以動態連接方式打包的時,則會觸發該錯誤。
問題緣由
Podfile 中不使用 use_frameworks! 時,每一個 pod 是會生成相應的 .a(靜態連接庫)文件,而後經過 static libraries 來管理 pod 代碼,在 Linked 時會包含該 pod 引用的其餘的 pod 的 .a 文件。
Podfile 中使用 use_frameworks! 時是會生成相應的 .framework 文件,而後經過 dynamic frameworks 的方式來管理 pod 代碼,在 Linked 時會包含該 pod 引用的其餘的 pod 的 .framework 文件。
上述場景中雖然以 framework 的方式引用了 B 組件,然而 B 組件其實是一個靜態庫,須要拷貝並連接到該 pod 中,然而 dynamic frameworks 方式並不會這麼作,因此就報錯了。
解決方案

  1. 修改 pod 庫中 podspec,增長 pod_target_xcconfig,定義好 FRAMEWORK_SEARCH_PATHSOTHER_LDFLAGS 兩個環境變量;
  2. hook verify_no_static_framework_transitive_dependencies 的方法,將其幹掉!對應 issue
  3. 修改 pod 庫中 podspec,開啓 static_framework 配置 s.static_framework = true
  1. verify_swift_pods_swift_version

確保 Swift Pod 的 Swift 版本正確配置且互相兼容的。

  1. verify_swift_pods_have_module_dependencies

檢測 Swift 庫的依賴庫是否支持了 module,這裏的 module 主要是針對 Objective-C 庫而言。
首先,Swift 是自然支持 module 系統來管理代碼的,Swift Module 是構建在 LLVM Module 之上的模塊系統。Swift 庫在解析後會生成對應的 modulemapumbrella.h 文件,這是 LLVM Module 的標配,一樣 Objective-C 也是支持 LLVM Module。當咱們以 Dynamic Framework 的方式引入 Objective-C 庫時,Xcode 支持配置並生成 header,而靜態庫 .a 須要本身編寫對應的 umbrella.hmodulemap
其次,若是你的 Swift Pod 依賴了 Objective-C 庫,又但願以靜態連接的方式來打包 Swift Pod 時,就須要保證 Objective-C 庫啓用了 modular_headers,這樣 CocoaPods 會爲咱們生成對應 modulemapumbrella.h 來支持 LLVM Module。你能夠從這個地址 - http://blog.cocoapods.org/CocoaPods-1.5.0/ 查看到更多細節。

  1. verify_no_pods_used_with_multiple_swift_versions

檢測是否全部的 Pod Target 中版本一致性問題。

用一個流程圖來歸納這一驗證環節:
validate targets

0x5 生成工程 (Integrate)

工程文件的生成是 pod install 的最後一步,他會將以前版本仲裁後的全部組件經過 Project 文件的形式組織起來,而且會對 Project 中作一些用戶指定的配置。

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

install 過程當中,除去依賴仲裁部分和下載部分的時間消耗,在工程文件生成也會有相對較大的時間開銷。這裏每每也是速度優化核心位置。

0x6 寫入依賴 (write_lockfiles)

將依賴更新寫入 Podfile.lockManifest.lock

0x7 結束回調(perform_post_install_action)

最後一步收尾工做,爲全部插件提供 post-installation 操做以及 hook。

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
    # 輸出結束信息 `Pod installation complete!`
    print_post_install_message
end

核心組件在 pod install 各階段的做用以下:

02-pod install integrate

總結

當咱們知道 CocoaPods 在 install 的大體過程後,咱們能夠對其作一些修改和控制。例如知道了插件的 pre_installpost_install 的具體時機,咱們就能夠在 Podfile 中執行對應的 Ruby 腳本,達到咱們的預期。同時瞭解 install 過程也有助於咱們進行每一個階段的性能分析,以優化和提升 Install 效率。

後續,將學習 CocoaPods 中每個組件的實現,將全部的問題在代碼中找到答案。

知識點問題梳理

這裏羅列了四個問題用來考察你是否已經掌握了這篇文章,若是沒有建議你加入收藏再次閱讀:

  1. 簡單概述 CocoaPods 的核心模塊?
  2. pod 命令是如何找到並啓動 CocoaPods 程序的?
  3. 簡述 pod install 流程?
  4. resolve_dependencies 階段中的 pre_download 是爲了解決什麼問題?
  5. validate_targets 都作了哪些校驗工做?
相關文章
相關標籤/搜索