2. 总体把握 CocoaPods 核心组件

2021年11月23日 阅读数:6
这篇文章主要向大家介绍2. 总体把握 CocoaPods 核心组件,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

本文做者:Edmondhtml

CocoaPods 历险记这个专题是 Edmond冬瓜 共同撰写,对于 iOS / macOS 工程中版本管理工具 CocoaPods 的实现细节、原理、源码、实践与经验的分享记录,旨在帮助你们可以更加了解这个依赖管理工具,而不只局限于 pod installpod updategit

本文知识目录

引子

在上文 版本管理工具及 Ruby 工具链环境 中,咱们聊到如何统一管理团队小伙伴的 CocoaPods 生产环境及使用到的 Ruby 工具链。今天让咱们将目光转到 CocoaPods 身上,一块儿来聊聊它的主要构成,以及各个组件在整个 Pods 工做流的关系。github

为了总体把握 CocoaPods 这个项目,建议你们去入门一下 Ruby 这门脚本语言。另外本文基于 CocoaPods 1.9.2 版本。web

CocoaPods 的核心组件

做为包管理工具,CocoaPods 随着 Apple 生态的蓬勃发展也在不断迭代和进化,而且各部分核心功能也都演化出相对独立的组件。这些功能独立的组件,均拆分出一个个独立的 Gem 包,而 CocoaPods 则是这些组件的“集大成者”。算法

CocoaPods 依赖总览

咱们知道在 Pod 管理的项目中,Podfile 文件里描述了它所依赖的 dependencies,相似的 Gem 的依赖能够在 Gemfile 中查看。那 CocoaPods 的 Gemfile 有哪些依赖呢?json

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 库,其方法以下:swift

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 加载。xcode

经过简单的目录分割和 Gemfile 管理,就实现了最基本又最直观的热插拔,对组件开发十分友好。因此你只要将多个仓库以下图方式排列,便可实现跨仓库组件开发:缓存

$ 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 中的核心开发组件:ruby

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[2] 虽然是一个简单的命令行解释器,但它提供了功能齐全的命令行界面和 API。它不只负责解析咱们使用到的 Pods 命令,如:pod install, pod update 等,还可用于封装经常使用的一些脚本,将其打包成简单的命令行小工具。

PS: 所谓命令行解释器就是从标准输入或者文件中读取命令并执行的程序。详见 Wiki[3]

cocoapods-core

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

CocoaPods-Core[4] 用于 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[5] 是用于下载源码的小工具,它支持各类类型的版本管理工具,包括 HTTP / SVN / Git / Mercurial。它能够提供 tagscommitesrevisionsbranches 以及 zips 文件的下载和解压缩操做。

Molinillo

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

Molinillo[6] 是 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[7] 可经过 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[8] 的手段调起咱们须要的 CocoaPods 工程。这是 RVM 的自身行为,它利用了 executable-hook[9] 来注入 Gems 插件来定制扩展。

PS:大多数动态语言都支持 eval 这一神奇的函数。自从 Lisp 开始就支持了,它经过接受一个字符串类型做为参数,将其解析成语句并混合在当前做用域内运行。详细能够参考这篇 文章[10]

在入口的最后部分,经过调用 Pod::Command.run(ARGV),实例化了一个 CLAide::Command 对象,开始咱们的 CLAide 命令解析阶段。这里不对 CLAide 这个命令解析工具作过多的分析,这个是后面系列文章的内容。这里咱们仅仅须要知道:

每一个 CLAide  命令的执行,最终都会对应到具体 Command Class 的 run 方法。

Pod 命令对应的 run 方法实现以下:

module Pod
  class Command
    class Install < Command
      # ... 
      def run
        # 判断是否存在 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 命令的主体部分,流程以下图:

对应的实现以下:

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, valuesbreak 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 异常。
  2. 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[11]

      3. 修改 pod 库中 podspec,开启 static_framework 配置 s.static_framework = true

  1. verify_swift_pods_swift_version
    确保 Swift Pod 的 Swift 版本正确配置且互相兼容的。
  2. verify_swift_pods_have_module_dependencies
    检测 Swift 库的依赖库是否支持了 module,这里的 module 主要是针对 Objective-C 库而言。首先,Swift 是自然支持 module 系统来管理代码的,Swift Module 是构建在 LLVM Module [12] 之上的模块系统。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/ [13] 查看到更多细节。
  3. verify_no_pods_used_with_multiple_swift_versions
    检测是否全部的 Pod Target 中版本一致性问题。

用一个流程图来归纳这一验证环节:

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 各阶段的做用以下:

总结

当咱们知道 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 都作了哪些校验工做?

参考资料

[1]

版本管理工具及 Ruby 工具链环境: https://zhuanlan.zhihu.com/p/147537112

[2]

CLAide: https://github.com/CocoaPods/CLAide

[3]

Wiki: https://www.wikiwand.com/en/Command-line_argument_parsing

[4]

CocoaPods-Core: https://github.com/CocoaPods/Core

[5]

Cocoapods-Downloader: https://github.com/CocoaPods/cocoapods-downloader

[6]

Molinillo: https://github.com/CocoaPods/Molinillo/blob/master/ARCHITECTURE.md

[7]

Xcodeproj: https://github.com/CocoaPods/Xcodeproj

[8]

eval: https://www.infoq.com/articles/eval-options-in-ruby/

[9]

executable-hook: https://github.com/rvm/executable-hooks

[10]

eval 相关介绍: https://ruby-china.org/topics/31465

[11]

对应 issue: https://github.com/CocoaPods/CocoaPods/issues/3289

[12]

LLVM Module: http://clang.llvm.org/docs/Modules.html

[13]

http://blog.cocoapods.org/CocoaPods-1.5.0/



本文分享自微信公众号 - 一瓜技术(tech_gua)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。