4. Podfile 的解析邏輯

本文做者:Edmond  
校對:冬瓜

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

本文知識目錄

引子

在上文 CocoaPods 命令解析 - CLAide」 中,咱們經過對 CLAide 的源碼分析,瞭解了 CocoaPods 是如何處理 pod 命令,多級命令又是如何組織和嵌套的,並解釋了命令行輸出所表明的含義。今天咱們開始學習 Podfileios

大多 iOS 工程師最早接觸到的 CocoaPods 概念應該是 Podfile,而 Podfile 屬於 cocoapods-core(如下簡稱 Core) 的兩大概念之一。另一個則是 Podspec[2] (用於描述 Pod Library 的配置文件),只有當你須要開發 Pod 組件的時候纔會接觸。git

在介紹 Podfile 的內容結構以前,必需要談談 Xcode 的工程結構。github

Xcode 工程結構

咱們先來看一個極簡 Podfile 聲明:web

target 'Demo' do
 pod 'Alamofire':path => './Alamofire'
end

它編譯後的工程目錄以下:算法

如你所見 Podfile 的配置是圍繞 Xcode 的這些工程結構:Workspace、Project、Target 以及 Build Setting 來展開的。做爲包管理工具 CocoaPods 將所管理的 Pods 依賴庫組裝成一個個 Target,統一放入 Pods project 中的 Demo target,並自動配置好 Target 間的依賴關係。swift

以後將 Example 主工程和 Pods 工程一塊兒打包到新建的 Example.workspace,配好主工程與 Pods 工程之間的依賴,完成最終轉換。vim

接下來,咱們來聊一聊這些 Xcode 結構:數組

Target - 最小可編譯單元

首先是 Target,它做爲工程中最小的可編譯單元,根據 Build Phases[3]Build Settings[4] 將源碼做爲輸入,經編譯後輸出結果產物。xcode

其輸出結果能夠是連接庫、可執行文件或者資源包等,具體細節以下:

  • Build Setting:好比指定使用的編譯器,目標平臺、編譯參數、頭文件搜索路徑等;
  • Build 時的前置依賴、執行的腳本文件;
  • Build 生成目標的簽名、Capabilities 等屬性;
  • Input:哪些源碼或者資源文件會被編譯打包;
  • Output:哪些靜態庫、動態庫會被連接;

Project - Targets 的載體

Project 就是一個獨立的 Xcode 工程,做爲一個或多個 Targets 的資源管理器,自己沒法被編譯。Project 所管理的資源都來自它所包含的 Targets。特色以下:

  • 至少包含一個或多個可編譯的 Target;
  • 爲所包含的 Targets 定義了一份默認編譯選項,若是 Target 有本身的配置,則會覆蓋 Project 的預設值;
  • 能將其餘 Project 做爲依賴嵌入其中;

下圖爲 Project 與所包含對 Targets 的關係

Workspace - 容器

做爲純粹的項目容器,Workspace 不參與任何編譯連接過程,僅用於管理同層級的 Project,其特色:

  • Workspace 能夠包含多個 Projects
  • 同一個 Workspace 中的 Proejct 文件對於其餘 Project 是默承認見的, 這些 Projcts 會共享 workspace build directory
  • 一個 Xcode Project 能夠被包含在多個不一樣的 Workspace 中,由於每一個 Project 都有獨立的 Identity,默認是 Project Name;

Scheme - 描述 Build 過程

Scheme 是對於整個 Build 過程的一個抽象,它描述了 Xcode 應該使用哪一種 Build Configurations[5] 、執行什麼任務、環境參數等來構建咱們所需的 Target。

Scheme 中預設了六個主要過程:Build、Run、Test、Profile、Analyze、Archive。包括了咱們對 Target 的全部操做,每個過程均可以單獨配置。

CocoaPods-Core

CocoaPods-Core 用於 CocoaPods 中配置文件的解析,包括 PodfilePodspec 以及解析後的依賴鎖存文件,如 Podfile.lock 等。

CocoaPods-Core 的文件構成

照例,咱們先經過入口文件 lib/cocoapods-core.rb 來一窺 Core 項目的主要文件:

module Pod
  require 'cocoapods-core/gem_version'

  class PlainInformative < StandardError; end
  class Informative < PlainInformative; end

  require 'pathname'
  require 'cocoapods-core/vendor'
   
  # 用於存儲 PodSpec 中的版本號
  autoload :Version,        'cocoapods-core/version'
  # pod 的版本限制
  autoload :Requirement,    'cocoapods-core/requirement'
  # 配置 Podfile 或 PodSpec 中的 pod 依賴
  autoload :Dependency,     'cocoapods-core/dependency'
  # 獲取 Github 倉庫信息
  autoload :GitHub,         'cocoapods-core/github'
  # 處理 HTTP 請求
  autoload :HTTP,           'cocoapods-core/http'
  # 記錄最終 pod 的依賴信息
  autoload :Lockfile,       'cocoapods-core/lockfile'
  # 記錄 SDK 的名稱和 target 版本
  autoload :Platform,       'cocoapods-core/platform'
  # 對應 Podfile 文件的 class
  autoload :Podfile,        'cocoapods-core/podfile'
  # 管理 PodSpec 的集合
  autoload :Source,         'cocoapods-core/source'
  # 管理基於 CDN 來源的 PodSpec 集合
  autoload :CDNSource,      'cocoapods-core/cdn_source'
  # 管理基於 Trunk 來源的 PodSpec 集合
  autoload :TrunkSource,    'cocoapods-core/trunk_source'
  # 對應 PodSpec 文件的 class
  autoload :Specification,  'cocoapods-core/specification'
  # 將 pod 信息轉爲 .yml 文件,用於 lockfile 的序列化
  autoload :YAMLHelper,     'cocoapods-core/yaml_helper'
  # 記錄 pod 依賴類型,是靜態庫/動態庫
  autoload :BuildType,      'cocoapods-core/build_type'
  
  ...

  Spec = Specification
end

將這些 Model 類按照對應的依賴關係進行劃分,層級以下:

Podfile 的主要數據結構

先來了解 Podfile 的主要數據結構

Specification

Specification 即存儲 PodSpec 的內容,是用於描述一個 Pod 庫的源代碼和資源將如何被打包編譯成連接庫或 framework,後續將會介紹更多的細節。

TargetDefinition

TargetDefinition 是一個多叉樹結構,每一個節點記錄着 Podfile 中定義的 Pod 的 Source 來源、Build Setting、Pod 子依賴等

該樹的根節點指向 Podfile,而 Podfile 中的 root_target_definitions 則記錄着全部的 TargetDefinition 的根節點,正常狀況下該 list 中只有一個 root 即 Pods.project

爲了便於閱讀,簡化了大量的 DSL 配置相關的方法和屬性並對代碼順序作了調整,大體結構以下:

module Pod
  class Podfile
    class TargetDefinition
      # 父節點: TargetDefinition 或者 Podfile
      attr_reader :parent
      # 子節點: TargetDefinition
      attr_reader :children
      # 記錄 tareget 的配置信息
      attr_accessor :internal_hash

      def root?
        parent.is_a?(Podfile) || parent.nil?
      end

      def root
        if root?
          self
        else
          parent.root
        end
      end

      def podfile
        root.parent
      end
       
      # ...
    end
  end
end

對應上一節 Xcode 工程結構中的 Podfile 關係以下:

CocoaPods 正是巧妙利用了 Xcode 工程結構的特色,引入  Pods.project 這一中間層,將主工程的 Pods 依賴所有轉接到 Pods.project 上,最後再將 Pods.project 做爲主項目的依賴。

儘管這麼作也受到了一些質疑和詬病(所謂的侵入性太強),但筆者的觀點是,正得益於 Pods.project 這一設計隔絕了第三方依賴庫對於主項目的頻繁更改,也便於後續的管理和更新,體現了軟件工程中的 開放-關閉原則

好比,在 Pod 1.7.0 版本中支持的 Multiple Xcodeproj Generation[6] 就是解決隨着項目的迭代而日益增大的 Pods project 的問題。

試想當你的項目中存在上百個依賴庫,每一個依賴庫的變動都會影響到你的主工程,這將是很是可怕的問題。

Podfile

Podfile 是用於描述一個或多個 Xcode Project 中各個 Targets 之間的依賴關係。

這些 Targets 的依賴關係對應的就是 TargetDefinition 樹中的各子節點的層級關係。如前面所說,有了 Podfile 這個根節點的指向,僅需對依賴樹進行遍歷,就能輕鬆獲取完整的依賴關係

有了這層依賴樹,對於某個 Pod 庫的更新便是對樹節點的更新,即可輕鬆的分析出這次更新涉及的影響。

簡化調整後的 Podfile 代碼以下:

require 'cocoapods-core/podfile/dsl'
require 'cocoapods-core/podfile/target_definition'

module Pod
  class Podfile
    include Pod::Podfile::DSL
    # podfile 路徑
    attr_accessor :defined_in_file
    # 全部的 TargetDefinition 的根節點, 正常只有一個,即 Pods.project target
    attr_accessor :root_target_definitions
    # 記錄 Pods.project 項目的配置信息
    attr_accessor :internal_hash
    # 當前 DSL 解析使用的 TargetDefinition
    attr_accessor :current_target_definition

    # ...
  end
end

直接看 dsl.rb,該文件內部定義了 Podfile DSL 支持的全部方法。經過 include 的使用將 Pod::Podfile::DSL 模塊 Mix-in 後插入到 Podfile 類中。想了解更多 Mix-in 特性,移步 「CocoaPods 中的 Ruby 特性之 Mix-in」

Lockfile

Lockfile,顧名思義是用於記錄最後一次 CocoaPods 所安裝的 Pod 依賴庫版本的信息快照。也就是生成的 Podfile.lock

pod install 過程,Podfile 會結合它來確認最終所安裝的 Pod 版本,固定 Pod 依賴庫版本防止其自動更新。

Lockfile 也做爲 Pods 狀態清單 (mainfest),用於記錄安裝過程的中哪些 Pod 須要被刪除或安裝或更新等。

以開頭的 Podfile 經 pod install 所生成的 Podfile.lock 爲例:

PODS:
  - Alamofire (4.6.0)

DEPENDENCIES:
  - Alamofire (from `./Alamofire`)

EXTERNAL SOURCES:
  Alamofire:
    :path: "./Alamofire"

SPEC CHECKSUMS:
  Alamofire: 0dda98a0ed7eec4bdcd5fe3cdd35fcd2b3022825

PODFILE CHECKSUM: da12cc12a30cfb48ebc5d14e8f51737ab65e8241

COCOAPODS: 1.10.0.beta.2

咱們來分析一下,經過該 Lockfile 可以獲取哪些信息:

Key 含義
PODS 記錄全部 Pod 庫的具體安裝版本號
DEPENDENCIES 記錄各 Pod 庫之間的相互依賴關係,因爲這裏只有 Alamofire 且它無其餘依賴,暫時無關看出區別
EXTERNAL SOURCES 記錄部分經過外部源的 Pod 庫(Git 引入、Path 引入)
SPEC CHECKSUMS 記錄當前各 Pod 庫的 Podspec 文件 Hash 值,其實就是文件的 md5
PODFILE CHECKSUM 記錄 Podfile 文件的 Hash 值,一樣是 md5,確認是否有變動
COCOAPODS 記錄上次所使用的 CocoaPods 版本

Podfile 內容加載

Podfile 文件類型

你能夠在 CocoaPods 的 /lib/cocoapods/config.rb 找到 Podfile 所支持的文件類型:

PODFILE_NAMES = [
   'CocoaPods.podfile.yaml',
   'CocoaPods.podfile',
   'Podfile',
   'Podfile.rb',
].freeze

CocoaPods 按照上述命名優先級來查找工程目錄下所對應的 Podfile 文件。當發現目錄中存在 CocoaPods.podfile.yaml 文件時會優先加載。

不少同窗可能只知道到 Podfile 支持 Ruby 的文件格式,而不瞭解它還支持了 YAML 格式。YAML 是 YAML Ain't Markup Language 的縮寫,其官方定義[7]以下:

它是一種面向工程師友好的序列化語言。咱們的 Lockfile 文件就是以 YAML 格式寫入 Podfile.lock 中的。

Podfile 文件讀取

回到 lib/cocoapods-core/podfile.rb 來看讀取方法:

module Pod
  class Podfile
    include Pod::Podfile::DSL

    def self.from_file(path)
      path = Pathname.new(path)
      unless path.exist?
        raise Informative, "No Podfile exists at path `#{path}`."
      end
      # 這裏咱們能夠看出,Podfile 目前已經支持告終尾是 .podfile 和 .rb 後綴的文件名
      # 實際上是爲了改善不少編譯器使用文件後綴來確認 filetype,好比 vim
      # 相比與 Podfile 這個文件名要更加的友好
      case path.extname
      when '''.podfile''.rb'
        Podfile.from_ruby(path)
      when '.yaml'
        # 如今也支持了 .yaml 格式
        Podfile.from_yaml(path)
      else
        raise Informative, "Unsupported Podfile format `#{path}`."
      end
    end
end

from_filepod install 命令執行後的 verify_podfile_exists! 中被調用的:

def verify_podfile_exists!
    unless config.podfile
        raise Informative, "No `Podfile' found in the project directory."
    end
end

Podfile 文件的讀取就是 config.podfile  裏觸發的,代碼在 CocoaPods 的 config.rb 文件中:

def podfile_path_in_dir(dir)
    PODFILE_NAMES.each do |filename|
        candidate = dir + filename
        if candidate.file?
        return candidate
        end
    end
    nil
end

def podfile_path
    @podfile_path ||= podfile_path_in_dir(installation_root)
end

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

這裏的方法 podfilepodfile_path 都是 lazy 加載的。最後 Core 的 from_file 將依據目錄下的 Podfile 文件類型選擇調用 from_yaml 或者 from_ruby

Pod::Command::Install 命令到 Podfile 文件加載的調用棧以下:

Podfile From Ruby 解析

當咱們經過 pod init 來初始化 CocoaPods 項目時,默認生成的 Podfile 名稱就是 Podfile,那就從 Podfile.from_ruby 開始。

def self.from_ruby(path, contents = nil)
    # ①
    contents ||= File.open(path, 'r:utf-8', &:read)
    # 兼容 1.9 版本的 Rubinius 中的編碼問題
    if contents.respond_to?(:encoding) && contents.encoding.name != 'UTF-8'
        contents.encode!('UTF-8')
    end

    # 對 Podfile 中不規範的單引號或雙引號進行檢查,並進行自動修正,及拋出錯誤
    if contents.tr!('「」‘’‛'%(""'''))
        CoreUI.warn "..."
    end
    # ②
    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 內容的讀取和編碼,同時對可能出現的單引號和雙引號的匹配問題進行了修正。pathblock 爲入參進行 podfile 類的初始化並將其放回,保存在全局的 config.podfile 中。

Tips: 若是要在 Ruby 對象的初始化中傳入參數,須要重載 Object 的 initialize[8] 方法,這裏的 Podfile.new(...) 本質上是 initialize 的方法調用。

initialize 方法所傳入的尾隨閉包 block 的核心在於內部的 eval 函數(在 CocoaPods 核心組件[9] 中有提到):

eval(contents, nil, path.to_s)

它將 Podfile 中的文本內容轉化爲方法執行,也就是說裏面的參數是一段 Ruby 的代碼字符串,經過 eval 方法能夠直接執行。繼續看 Podfile 的 initialize 方法:

def initialize(defined_in_file = nil, internal_hash = {}, &block)
    self.defined_in_file = defined_in_file
    @internal_hash = internal_hash
    if block
        default_target_def = TargetDefinition.new('Pods'self)
        default_target_def.abstract = true
        @root_target_definitions = [default_target_def]
        @current_target_definition = default_target_def
        instance_eval(&block)
    else
        @root_target_definitions = []
    end
end

它定義了三個參數:

參數 定義
defined_in_file Podfile 文件路徑
internal_hash 經過 yaml 序列化獲得的 Podfile 配置信息,保存在 internal_hash
block 用於映射 Podfile 的 DSL 配置

須要注意的是,經過 from_ruby 初始化的 Podfile 只傳入了參數 1 和 3,參數 2 internal_hash 則是提供給 from_yaml 的。

block 存在,會初始化名爲 Pods 的 TargetDefinition 對象,用於保存 Pods project 的相關信息和 Pod 依賴。而後調用 instance_eval[10] 執行傳入的 block,將 Podfile 的 DSL 內容轉換成對應的方法和參數,最終將參數存入 internal_hash 和對應的 target_definitions 中。

Tips: 在 Ruby 中存在兩種不一樣的方式來執行代碼塊 block,分別是 instance_evalclass_evalclass_eval 的執行上下文與調用類相關,調用者是類名或者模塊名,而 instance_eval 的調用者能夠是類的實例或者類自己。細節看 StackOverFlow[11]

Podfile From YAML 解析

YAML 格式的 Podfile 加載須要藉助 YAMLHelper 類來完成,YAMLHelper 則是基於 yaml[12] 的簡單封裝。

def self.from_yaml(path)
    string = File.open(path, 'r:utf-8', &:read)
  
    # 爲了解決 Rubinius incomplete encoding in 1.9 mode
   # https://github.com/rubinius/rubinius/issues/1539
    if string.respond_to?(:encoding) && string.encoding.name != 'UTF-8'
        string.encode!('UTF-8')
    end
    hash = YAMLHelper.load_string(string)
    from_hash(hash, path)
end

def self.from_hash(hash, path = nil)
    internal_hash = hash.dup
    target_definitions = internal_hash.delete('target_definitions'|| []
    podfile = Podfile.new(path, internal_hash)
    target_definitions.each do |definition_hash|
        definition = TargetDefinition.from_hash(definition_hash, podfile)
        podfile.root_target_definitions << definition
    end
    podfile
end

經過 from_yaml 將文件內容轉成 Ruby hash 後轉入 from_hash 方法。

區別於 from_ruby,這裏調用的 initialize 將讀取的 hash 直接存入 internal_hash,而後利用 TargetDefinition.from_hash 來完成的 hash 內容到 targets 的轉換,所以,這裏無需傳入 block 進行 DSL 解析和方法轉換。

Podfile 內容解析

前面提到 Podfile 的內容最終保存在 internal_hashtarget_definitions 中,本質上都是使用了 hash 來保存數據。因爲 YAML 文件格式的 Podfile 加載後就是 hash 對象,無需過多加工。惟一須要處理的是遞歸調用 TargetDefinition 的 from_hash 方法來解析 target 子節點的數據。

所以,接下來的內容解析主要針對 Ruby 文件格式的 DSL 解析,咱們以 pod 方法爲例:

target 'Example' do
 pod 'Alamofire'
end

當解析到 pod 'Alamofire' 時,會先經過 eval(contents, nil, path.to_s 將其轉換爲 dsl.rb 中的方法:

def pod(name = nil, *requirements)
    unless name
        raise StandardError, 'A dependency requires a name.'
    end
    current_target_definition.store_pod(name, *requirements)
end

name 爲 Alamofire,因爲咱們沒有指定對應的 Alamofire 版本,默認會使用最新版本。requirements  是控制 該 pod 來源獲取或者 pod target 的編譯選項等,例如:

pod 'Alamofire''0.9'
pod 'Alamofire':modular_headers => true
pod 'Alamofire':configurations => ['Debug''Beta']
pod 'Alamofire':source => 'https://github.com/CocoaPods/Specs.git'
pod 'Alamofire':subspecs => ['Attribute''QuerySet']
pod 'Alamofire':testspecs => ['UnitTests''SomeOtherTests']
pod 'Alamofire':path => '~/Documents/AFNetworking'
pod 'Alamofire':podspec => 'https://example.com/Alamofire.podspec'
pod 'Alamofire':git => 'https://github.com/looseyi/Alamofire.git':tag => '0.7.0'

Tips:requirements 最終是以 Gem::Requirement 對象來保存的。關於 pod 詳細說明請移步:Podfile 手冊[13]

對 name 進行校驗後,直接轉入 current_target_definition 畢竟 Pod 庫都是存在 Pods.project 之下:

def store_pod(name, *requirements)
  return if parse_subspecs(name, requirements) # This parse method must be called first
  parse_inhibit_warnings(name, requirements)
  parse_modular_headers(name, requirements)
  parse_configuration_whitelist(name, requirements)
  parse_project_name(name, requirements)

  if requirements && !requirements.empty?
    pod = { name => requirements }
  else
    pod = name
  end

  get_hash_value('dependencies', []) << pod
  nil
end

def get_hash_value(key, base_value = nil)
  unless HASH_KEYS.include?(key)
    raise StandardError, "Unsupported hash key `#{key}`"
  end
  internal_hash[key] = base_value if internal_hash[key].nil?
  internal_hash[key]
end

def set_hash_value(key, value)
  unless HASH_KEYS.include?(key)
    raise StandardError, "Unsupported hash key `#{key}`"
  end
  internal_hash[key] = value
end

通過一系列檢查以後,調用 get_hash_value 獲取 internal_hashdependencies,並將 name 和 requirements 選項存入。

這裏的 dependencies key 是定義在 TargetDefinition 文件的 HASH_KEYS,表示 Core 所支持的配置參數:

# freeze 表示該數組不可修改。另外,%w 用於表示其中元素被單引號括起的數組。 
# %W(#{foo} Bar Bar\ with\ space) => ["Foo", "Bar", "Bar with space"] 
# 對應的還有 %W 表示其中元素被雙引號括起的數組。
HASH_KEYS = %w(
    name
    platform
    podspecs
    exclusive
    link_with
    link_with_first_target
    inhibit_warnings
    use_modular_headers
    user_project_path
    build_configurations
    project_names
    dependencies
    script_phases
    children
    configuration_pod_whitelist
    uses_frameworks
    swift_version_requirements
    inheritance
    abstract
    swift_version
)
.freeze

整個映射過程以下:

精細化的 Podfile 配置

最後一節讓咱們來 Show 一下 ,看看 Podfile 所謂的 targets 之間的依賴關係能夠玩出什麼花來 😂 。

Target 嵌套

最簡單的 Podfile 就是文章開頭所展現的,不過在 Podfile 中還能夠對 Target 進行嵌套使用。

假設在咱們的主工程同時維護了三個項目,它們都依賴了 Alamofire,經過俄羅斯套娃就能輕鬆知足條件:

target 'Demo1' do
  pod 'Alamofire'

  target 'Demo2' do
    target 'Demo3' do
    end
  end
end

編譯後的 Pods.project 項目結構以下:

咱們知道,CocoaPods 在 Pods.project 中爲每一個在 Podfile 中聲明的 Target 生成一個與之對應的專屬 Target 來集成它的 Pod 依賴。

對於有依賴關係的 Target 其生成的專屬 Target 名稱則會按照依賴關係疊加來命名,如  target Demo3 的專屬 Target 名稱爲 Pods-Demo1-Demo2-Demo3。安裝完成後主項目將會引入該專屬 Target 來完成依賴關聯,如 Demo3:

關於 Target 嵌套,一個父節點是能夠有多個子節點的:

target 'Demo1' do
  pod 'Alamofire'

  target 'Demo2' do
    pod 'RxSwift'
  end
  target 'Demo3' do
   pod 'SwiftyJSON'
  end
end

Abstract Target

上面例子中,因爲 Demo1 與 Demo2 都須要依賴 Alamofire,咱們經過 Target 嵌套讓 Demo2 來繼承 Demo1 的 Pods 庫依賴。

這麼作可能會有一個限制,就是當 Demo1 的 Pod 依賴並不是所有爲 Demo2 所須要的時候,就會有依賴冗餘。此時就須要 Abstract Target 登場了。例如:

abstract_target 'Networking' do
  pod 'Alamofire'

  target 'Demo1' do
    pod 'RxSwift'
  end
  target 'Demo2' do
    pod 'ReactCocoa'
  end
  target 'Demo3' do
  end
end

將網絡請求的 Pod 依賴抽象到 Networking target 中,這樣就能避免 Demo2 對 RxSwift 的依賴。

這種方式配置所生成的 Pods.project 並不會存在名稱爲 Networking 的 Target,它僅會在主工程的專屬 Target 中留下印記:

總結

本文結合 Xcode 工程結構來展開 CocoaPods-Core 的 Podfile 之旅,主要感覺以下:

  1. 再一次感覺了 Ruby 語言的動態之美,給我一個字符串,還你一個未知世界;
  2. 結合 Xcode 工程結構更好的理解了 Podfile 的設計初衷, 基礎知識很重要;
  3. 所謂「算法無用論」這種事情,在計算機的世界是不存在的,沒有好的數據結構知識如何更好的抽象;
  4. 瞭解 Podfile 的 DSL 是如何映射到內存中,又是如何來存儲每一個關鍵數據的

知識點問題梳理

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

  1. 說說 TargetDefinition 的數據結構 ?
  2. 說說 TargetDefinition 與 Xcode Project 的關係 ?
  3. Podfile 的文件格式有幾種,分別是如何加載 ?
  4. LockfilePodfile 的關係

參考資料

[1]

CocoaPods 命令解析: https://zhuanlan.zhihu.com/p/212101448

[2]

Podspec: https://guides.cocoapods.org/syntax/podspec.html

[3]

Build Phases: https://www.objc.io/issues/6-build-tools/build-process/#controlling-the-build-process

[4]

Build Settings: https://developer.apple.com/library/archive/featuredarticles/XcodeConcepts/Concept-Build_Settings.html#//apple_ref/doc/uid/TP40009328-CH6-SW1

[5]

Build Configurations: https://medium.com/practical-ios-development/some-practical-uses-for-xcode-build-schemes-and-build-configurations-swift-e50d15a1304f

[6]

Multiple Xcodeproj Generation: http://blog.cocoapods.org/CocoaPods-1.7.0-beta/

[7]

官方定義: https://yaml.org/

[8]

initialize: https://ruby-doc.org/docs/ruby-doc-bundle/UsersGuide/rg/objinitialization.html

[9]

CocoaPods 核心組件: https://zhuanlan.zhihu.com/p/187272448

[10]

instance_eval: https://ruby-doc.org/core-2.7.0/BasicObject.html

[11]

StackOverFlow: https://stackoverflow.com/questions/900419/how-to-understand-the-difference-between-class-eval-and-instance-eval

[12]

yaml: https://github.com/ruby/yaml

[13]

Podfile 手冊: https://guides.cocoapods.org/syntax/podfile.html#pod





本文分享自微信公衆號 - 一瓜技術(tech_gua)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索