国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

CocoaPods 在iOS開發(fā)中養(yǎng)活了這么多項(xiàng)目,它到底是個(gè)啥?

這篇具有很好參考價(jià)值的文章主要介紹了CocoaPods 在iOS開發(fā)中養(yǎng)活了這么多項(xiàng)目,它到底是個(gè)啥?。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

對(duì)于iOS開發(fā)者而言,CocoaPods并不陌生,通過pod相關(guān)的命令操作,就可以很方便的將項(xiàng)目中用到的三方依賴庫資源集成到項(xiàng)目環(huán)境中,大大的提升了開發(fā)的效率。CocoaPods作為iOS項(xiàng)目的包管理工具,它在命令行背后做了什么操作?而又是通過什么樣的方式將命令指令聲明出來供我們使用的?這些實(shí)現(xiàn)的背后底層邏輯是什么?都是本文想要探討挖掘的。

一、Ruby是如何讓系統(tǒng)能夠識(shí)別已經(jīng)安裝的Pods指令的?

我們都知道在使用CocoaPods管理項(xiàng)目三方庫之前,需要安裝Ruby環(huán)境,同時(shí)基于Ruby的包管理工具gem再去安裝CocoaPods。通過安裝過程可以看出來,CocoaPods本質(zhì)就是Ruby的一個(gè)gem包。而安裝Cocoapods的時(shí)候,使用了以下的安裝命令:

sudo gem install cocoapods

安裝完成之后,就可以使用基于Cocoapods的 pod xxxx 相關(guān)命令了。gem install xxx 到底做了什么也能讓 Terminal 正常的識(shí)別 pod 命令?gem的工作原理又是什么?了解這些之前,可以先看一下 RubyGems 的環(huán)境配置,通過以下的命令:

gem environment

通過以上的命令,可以看到Ruby的版本信息,RubyGem的版本,以及gems包安裝的路徑,進(jìn)入安裝路徑 /Library/Ruby/Gems/2.6.0 后,我們能看到當(dāng)前的Ruby環(huán)境下所安裝的擴(kuò)展包,這里能看到我們熟悉的Cocoapods相關(guān)的功能包。除了安裝包路徑之外,還有一個(gè) EXECUTABLE DIRECTORY 執(zhí)行目錄 /usr/local/bin,可以看到擁有可執(zhí)行權(quán)限的pod文件,如下:

預(yù)覽一下pod文件內(nèi)容:

#!/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/bin/ruby
#
# This file was generated by RubyGems.
#
# The application 'cocoapods' is installed as part of a gem, and
# this file is here to facilitate running it.
#

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

根據(jù)文件注釋內(nèi)容可以發(fā)現(xiàn),當(dāng)前的可執(zhí)行文件是 RubyGems 在安裝 Cocoapods 的時(shí)候自動(dòng)生成的,同時(shí)會(huì)將當(dāng)前的執(zhí)行文件放到系統(tǒng)的環(huán)境變量路徑中,也即存放到了 /usr/local/bin 中了,這也就解釋了為什么我們通過gem安裝cocoapods之后,就立馬能夠識(shí)別pod可執(zhí)行環(huán)境了。

雖然能夠識(shí)別pod可執(zhí)行文件,但是具體的命令參數(shù)是如何進(jìn)行識(shí)別與實(shí)現(xiàn)呢?繼續(xù)看以上的pod的文件源碼,會(huì)發(fā)現(xiàn)最終都指向了 Gemactivate_bin_pathbin_path 方法,為了搞清楚Gem到底做了什么,在官方的RubyGems源碼的rubygems.rb 文件中找到了兩個(gè)方法的相關(guān)定義與實(shí)現(xiàn),摘取了主要的幾個(gè)方法實(shí)現(xiàn),內(nèi)容如下:

  ##
  # Find the full path to the executable for gem +name+.  If the +exec_name+
  # is not given, an exception will be raised, otherwise the
  # specified executable's path is returned.  +requirements+ allows
  # you to specify specific gem versions.
  #
  # A side effect of this method is that it will activate the gem that
  # contains the executable.
  #
  # This method should *only* be used in bin stub files.
  def self.activate_bin_path(name, exec_name = nil, *requirements) # :nodoc:
    spec = find_spec_for_exe name, exec_name, requirements
    Gem::LOADED_SPECS_MUTEX.synchronize do
      spec.activate
      finish_resolve
    end
    spec.bin_file exec_name
  end

  def self.find_spec_for_exe(name, exec_name, requirements)
	#如果沒有提供可執(zhí)行文件的名稱,則拋出異常
    raise ArgumentError, "you must supply exec_name" unless exec_name
    # 創(chuàng)建一個(gè)Dependency對(duì)象
    dep = Gem::Dependency.new name, requirements
    # 獲取已經(jīng)加載的gem
    loaded = Gem.loaded_specs[name]
    # 存在直接返回
    return loaded if loaded && dep.matches_spec?(loaded)
    # 查找復(fù)合條件的gem配置
    specs = dep.matching_specs(true)
    specs = specs.find_all do |spec|
	  # 匹配exec_name 執(zhí)行名字,如果匹配結(jié)束查找
      spec.executables.include? exec_name
    end if exec_name
	# 如果沒有找到符合條件的gem,拋出異常
    unless spec = specs.first
      msg = "can't find gem #{dep} with executable #{exec_name}"
      raise Gem::GemNotFoundException, msg
    end
	#返回結(jié)果
    spec
  end
  private_class_method :find_spec_for_exe

  ##
  # Find the full path to the executable for gem +name+.  If the +exec_name+
  # is not given, an exception will be raised, otherwise the
  # specified executable's path is returned.  +requirements+ allows
  # you to specify specific gem versions.

  def self.bin_path(name, exec_name = nil, *requirements)
    requirements = Gem::Requirement.default if
      requirements.empty?
    # 通過exec_name 查找gem中可執(zhí)行文件
    find_spec_for_exe(name, exec_name, requirements).bin_file exec_name
  end
  
class Gem::Dependency
  def matching_specs(platform_only = false)
    env_req = Gem.env_requirement(name)
    matches = Gem::Specification.stubs_for(name).find_all do |spec|
      requirement.satisfied_by?(spec.version) && env_req.satisfied_by?(spec.version)
    end.map(&:to_spec)

    if prioritizes_bundler?
      require_relative "bundler_version_finder"
      Gem::BundlerVersionFinder.prioritize!(matches)
    end

    if platform_only
      matches.reject! do |spec|
        spec.nil? || !Gem::Platform.match_spec?(spec)
      end
    end

    matches
  end
end

class Gem::Specification < Gem::BasicSpecification
  def self.stubs_for(name)
    if @@stubs
      @@stubs_by_name[name] || []
    else
      @@stubs_by_name[name] ||= stubs_for_pattern("#{name}-*.gemspec").select do |s|
        s.name == name
      end
    end
  end

end 


通過當(dāng)前的實(shí)現(xiàn)可以看出在兩個(gè)方法實(shí)現(xiàn)中,通過 find_spec_for_exe 方法依據(jù)名稱name查找sepc對(duì)象,匹配成功之后返回sepc對(duì)象,最終通過spec對(duì)象中的bin_file方法來進(jìn)行執(zhí)行相關(guān)的命令。以下為gems安裝的配置目錄集合:

注:bin_file 方法的實(shí)現(xiàn)方式取決于 gem 包的類型和所使用的操作系統(tǒng)。在大多數(shù)情況下,它會(huì)根據(jù)操作系統(tǒng)的不同,使用不同的查找算法來確定二進(jìn)制文件的路徑。例如,在Windows上,它會(huì)搜索 gem包的 bin 目錄,而在 Unix 上,它會(huì)搜索 gem 包的 bin目錄和 PATH 環(huán)境變量中的路徑。

通過當(dāng)前的實(shí)現(xiàn)可以看出在兩個(gè)方法實(shí)現(xiàn)中,find_spec_for_exe 方法會(huì)遍歷所有已安裝的 gem 包,查找其中包含指定可執(zhí)行文件的 gem 包。如果找到了匹配的 gem 包,則會(huì)返回該 gem 包的 Gem::Specification 對(duì)象,并調(diào)用其 bin_file 方法獲取二進(jìn)制文件路徑。而 bin_file 是在 Gem::Specification 類中定義的。它是一個(gè)實(shí)例方法,用于查找與指定的可執(zhí)行文件 exec_name 相關(guān)聯(lián)的 gem 包的二進(jìn)制文件路徑,定義實(shí)現(xiàn)如下:

  def bin_dir
    @bin_dir ||= File.join gem_dir, bindir
  end
  ##
  # Returns the full path to installed gem's bin directory.
  #
  # NOTE: do not confuse this with +bindir+, which is just 'bin', not
  # a full path.
  def bin_file(name)
    File.join bin_dir, name
  end

到這里,可以看出,pod命令本質(zhì)是執(zhí)行了RubyGems 的 find_spec_for_exe 方法,用來查找并執(zhí)行g(shù)ems安裝目錄下的bin目錄,也即是 /Library/Ruby/Gems/2.6.0 目錄下的gem包下的bin目錄。而針對(duì)于pod的gem包,如下所示:

至此,可以發(fā)現(xiàn),由系統(tǒng)執(zhí)行環(huán)境 /usr/local/bin 中的可執(zhí)行文件 pod 引導(dǎo)觸發(fā),Ruby通過 Gem.bin_path("cocoapods", "pod", version) 與 Gem.activate_bin_path('cocoapods', 'pod', version) 進(jìn)行轉(zhuǎn)發(fā),再到gems包安裝目錄的gem查找方法 find_spec_for_exe,最終轉(zhuǎn)到gems安裝包下的bin目錄的執(zhí)行文件進(jìn)行命令的最終執(zhí)行,流程大致如下:

而對(duì)于pod的命令又是如何進(jìn)行識(shí)別區(qū)分的呢?剛剛的分析可以看出對(duì)于gems安裝包的bin下的執(zhí)行文件才是最終的執(zhí)行內(nèi)容,打開cocoapod的bin目錄下的pod可執(zhí)行文件,如下:

#!/usr/bin/env ruby

if Encoding.default_external != Encoding::UTF_8

  if ARGV.include? '--no-ansi'
    STDERR.puts <<-DOC
    WARNING: CocoaPods requires your terminal to be using UTF-8 encoding.
    Consider adding the following to ~/.profile:

    export LANG=en_US.UTF-8
    DOC
  else
    STDERR.puts <<-DOC
    \e[33mWARNING: CocoaPods requires your terminal to be using UTF-8 encoding.
    Consider adding the following to ~/.profile:

    export LANG=en_US.UTF-8
    \e[0m
    DOC
  end

end

if $PROGRAM_NAME == __FILE__ && !ENV['COCOAPODS_NO_BUNDLER']
  ENV['BUNDLE_GEMFILE'] = File.expand_path('../../Gemfile', __FILE__)
  require 'rubygems'
  require 'bundler/setup'
  $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
elsif ENV['COCOAPODS_NO_BUNDLER']
  require 'rubygems'
  gem 'cocoapods'
end

STDOUT.sync = true if ENV['CP_STDOUT_SYNC'] == 'TRUE'

require 'cocoapods'

# 環(huán)境變量判斷是否配置了profile_filename,如果配置了按照配置內(nèi)容生成
if profile_filename = ENV['COCOAPODS_PROFILE']
  require 'ruby-prof'
  reporter =
    case (profile_extname = File.extname(profile_filename))
    when '.txt'
      RubyProf::FlatPrinterWithLineNumbers
    when '.html'
      RubyProf::GraphHtmlPrinter
    when '.callgrind'
      RubyProf::CallTreePrinter
    else
      raise "Unknown profiler format indicated by extension: #{profile_extname}"
    end
  File.open(profile_filename, 'w') do |io|
    reporter.new(RubyProf.profile { Pod::Command.run(ARGV) }).print(io)
  end
else
  Pod::Command.run(ARGV)
end


可以發(fā)現(xiàn),pod命令參數(shù)的解析運(yùn)行是通過 Pod::Command.run(ARGV) 實(shí)現(xiàn)的。通過該線索,我們接著查看Pod庫源碼的Command類的run方法都做了什么?該類在官方源碼的lib/cocoapods/command.rb 定義的,摘取了部分內(nèi)容如下:

  class Command < CLAide::Command
    def self.run(argv)
      ensure_not_root_or_allowed! argv
      verify_minimum_git_version!
      verify_xcode_license_approved!
      super(argv)
    ensure
      UI.print_warnings
    end
  end

源碼中在進(jìn)行命令解析之前,進(jìn)行了前置條件檢查判斷: 1、檢查當(dāng)前用戶是否為 root 用戶或是否在允許的用戶列表中 2、檢查當(dāng)前系統(tǒng)上安裝的 Git 版本是否符合最低要求 3、檢查當(dāng)前系統(tǒng)上的 Xcode 許可是否已經(jīng)授權(quán)

如果都沒有問題,則會(huì)調(diào)用父類的 run 方法,而命令的解析可以看出來應(yīng)該是在其父類 CLAide::Command 進(jìn)行的,CLAideCocoaPods的命令行解析庫,在 command.rb 文件中,可以找到如下 Command 類的實(shí)現(xiàn):


    def initialize(argv)
      argv = ARGV.coerce(argv)
      @verbose = argv.flag?('verbose')
      @ansi_output = argv.flag?('ansi', Command.ansi_output?)
      @argv = argv
      @help_arg = argv.flag?('help')
    end
    
    def self.run(argv = [])
      plugin_prefixes.each do |plugin_prefix|
        PluginManager.load_plugins(plugin_prefix)
      end
      # 轉(zhuǎn)換成ARGV對(duì)象
      argv = ARGV.coerce(argv)
      # 處理有效命令行參數(shù)
      command = parse(argv)
      ANSI.disabled = !command.ansi_output?
      unless command.handle_root_options(argv)
	    # 命令處理
        command.validate!
        # 運(yùn)行命令(由子類進(jìn)行繼承實(shí)現(xiàn)運(yùn)行)
        command.run
      end
    rescue Object => exception
      handle_exception(command, exception)
    end

    def self.parse(argv)
      argv = ARGV.coerce(argv)
      cmd = argv.arguments.first
      # 命令存在,且子命令存在,進(jìn)行再次解析
      if cmd && subcommand = find_subcommand(cmd)
	    # 移除第一個(gè)參數(shù)
        argv.shift_argument
        # 解析子命令
        subcommand.parse(argv)
	  # 不能執(zhí)行的命令直接加載默認(rèn)命令
      elsif abstract_command? && default_subcommand
        load_default_subcommand(argv)
      # 無內(nèi)容則創(chuàng)建一個(gè)comand實(shí)例返回
      else
        new(argv)
      end
    end
    # 抽象方法,由其子類進(jìn)行實(shí)現(xiàn)
    def run
      raise 'A subclass should override the `CLAide::Command#run` method to ' \
        'actually perform some work.'
    end
	# 返回 [CLAide::Command, nil]
    def self.find_subcommand(name)
      subcommands_for_command_lookup.find { |sc| sc.command == name }
    end

通過將 argv 轉(zhuǎn)換為 ARGV 對(duì)象(ARGV 是一個(gè) Ruby 內(nèi)置的全局變量,它是一個(gè)數(shù)組,包含了從命令行傳遞給 Ruby 程序的參數(shù)。例如:ARGV[0] 表示第一個(gè)參數(shù),ARGV[1] 表示第二個(gè)參數(shù),以此類推),然后獲取第一個(gè)參數(shù)作為命令名稱 cmd。如果 cmd 存在,并且能夠找到對(duì)應(yīng)的子命令 subcommand,則將 argv 中的第一個(gè)參數(shù)移除,并調(diào)用 subcommand.parse(argv) 方法解析剩余的參數(shù)。如果沒有指定命令或者找不到對(duì)應(yīng)的子命令,但當(dāng)前命令是一個(gè)抽象命令(即不能直接執(zhí)行),并且有默認(rèn)的子命令,則加載默認(rèn)子命令并解析參數(shù)。否則,創(chuàng)建一個(gè)新的實(shí)例,并將 argv 作為參數(shù)傳遞給它。

最終在轉(zhuǎn)換完成之后,通過調(diào)用抽象方法run 調(diào)用子類的實(shí)現(xiàn)來執(zhí)行解析后的指令內(nèi)容。到這里,順其自然的就想到了Cocoapods的相關(guān)指令實(shí)現(xiàn)必然繼承自了CLAide::Command 類,并實(shí)現(xiàn)了其抽象方法 run。為了驗(yàn)證這個(gè)推斷,我們接著看Cocoapods的源碼,在文件 Install.rb 中,有這個(gè) Install 類的定義與實(shí)現(xiàn),摘取了核心內(nèi)容:

module Pod
  class Command
    class Install < Command
      include RepoUpdate
      include ProjectDirectory
      
      def self.options
        [
          ['--repo-update', 'Force running `pod repo update` before install'],
          ['--deployment', 'Disallow any changes to the Podfile or the Podfile.lock during installation'],
          ['--clean-install', 'Ignore the contents of the project cache and force a full pod installation. This only ' \
            'applies to projects that have enabled incremental installation'],
        ].concat(super).reject { |(name, _)| name == '--no-repo-update' }
      end

      def initialize(argv)
        super
        @deployment = argv.flag?('deployment', false)
        @clean_install = argv.flag?('clean-install', false)
      end
	  # 實(shí)現(xiàn)CLAide::Command 的抽象方法
      def run
        # 驗(yàn)證工程目錄podfile 是否存在
        verify_podfile_exists!
        # 獲取installer對(duì)象
        installer = installer_for_config
        # 更新pods倉庫
        installer.repo_update = repo_update?(:default => false)
        # 設(shè)置更新標(biāo)識(shí)為關(guān)閉
        installer.update = false
        # 透?jìng)饕蕾囋O(shè)置
        installer.deployment = @deployment
        # 透?jìng)髟O(shè)置
        installer.clean_install = @clean_install
        installer.install!
      end
    end
  end
end

通過源碼可以看出,cocoaPods的命令解析是通過自身的 CLAide::Command 進(jìn)行解析處理的,而最終的命令實(shí)現(xiàn)則是通過繼承自 Command 的子類,通過實(shí)現(xiàn)抽象方法 run 來實(shí)現(xiàn)的具體命令功能的。到這里,關(guān)于Pod 命令的識(shí)別以及Pod 命令的解析與運(yùn)行是不是非常清晰了。

階段性小結(jié)一下,我們?cè)赥erminal中進(jìn)行pod命令運(yùn)行的過程中,背后都經(jīng)歷了哪些過程?整個(gè)運(yùn)行過程可以簡(jiǎn)述如下: 1、通過Gem生成在系統(tǒng)環(huán)境目錄下的可執(zhí)行文件 pod,通過該文件引導(dǎo) RubyGems 查找 gems包目錄下的sepc配置對(duì)象,也即是cocoaPods的sepc配置對(duì)象 2、查找到配置對(duì)象,通過bin_file方法查找cocoaPods包路徑中bin下的可執(zhí)行文件 3、運(yùn)行rubygems對(duì)應(yīng)cocoaPods的gem安裝包目錄中bin下的二進(jìn)制可執(zhí)行文件pod 4、通過執(zhí)行 Pod::Command.run(ARGV) 解析命令與參數(shù)并找出最終的 Command 對(duì)象執(zhí)行其run方法 5、在繼承自Command的子類的run實(shí)現(xiàn)中完成各個(gè)命令行指令的實(shí)現(xiàn)

以上的13階段實(shí)際上是Ruby的指令轉(zhuǎn)發(fā)過程,最終將命令轉(zhuǎn)發(fā)給了對(duì)應(yīng)的gems包進(jìn)行最終的處理。而45則是整個(gè)的處理過程。同時(shí)在Cocoapods的源碼實(shí)現(xiàn)中,可以發(fā)現(xiàn)每個(gè)命令都對(duì)應(yīng)一個(gè) Ruby 類,該類繼承自 CLAide::Command 類。通過繼承當(dāng)前類,可以定義該命令所支持的選項(xiàng)和參數(shù),并在執(zhí)行命令時(shí)解析這些選項(xiàng)和參數(shù)。

二、Ruby 是如何動(dòng)態(tài)生成可執(zhí)行文件并集成到系統(tǒng)環(huán)境變量中的?

剛剛在上一節(jié)賣了個(gè)關(guān)子,在安裝完成Ruby的gem包之后,在系統(tǒng)環(huán)境變量中就自動(dòng)生成了相關(guān)的可執(zhí)行文件命令。那么Ruby在這個(gè)過程中又做了什么呢?既然是在gem安裝的時(shí)候會(huì)動(dòng)態(tài)生成,不如就以gem的安裝命令 sudo gem install xxx 作為切入點(diǎn)去看相關(guān)的處理過程。我們進(jìn)入系統(tǒng)環(huán)境變量路徑 /usr/bin 找到 Gem 可執(zhí)行二進(jìn)制文件,如下:

打開gem,它的內(nèi)容如下:

#!/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/bin/ruby
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++

require 'rubygems'
require 'rubygems/gem_runner'
require 'rubygems/exceptions'

required_version = Gem::Requirement.new ">= 1.8.7"

unless required_version.satisfied_by? Gem.ruby_version then
  abort "Expected Ruby Version #{required_version}, is #{Gem.ruby_version}"
end

args = ARGV.clone

begin
  Gem::GemRunner.new.run args
rescue Gem::SystemExitException => e
  exit e.exit_code
end


可以發(fā)現(xiàn)最終通過執(zhí)行 Gem::GemRunner.new.run args 來完成安裝,顯然安裝的過程就在 Gem::GemRunner 類中。依舊查看RubyGems的源碼,在 gem_runner.rb 中,有著以下的定義:

def run(args)
    build_args = extract_build_args args

    do_configuration args

    begin
      Gem.load_env_plugins
    rescue StandardError
      nil
    end
    Gem.load_plugins

    cmd = @command_manager_class.instance

    cmd.command_names.each do |command_name|
      config_args = Gem.configuration[command_name]
      config_args = case config_args
                    when String
                      config_args.split " "
                    else
                      Array(config_args)
      end
      Gem::Command.add_specific_extra_args command_name, config_args
    end

    cmd.run Gem.configuration.args, build_args
  end

可以看出來命令的執(zhí)行最終轉(zhuǎn)到了 cmd.run Gem.configuration.args, build_args 的方法調(diào)用上,cmd是通過 @command_manager_class 進(jìn)行裝飾的類,找到其裝飾的地方如下:

def initialize
    @command_manager_class = Gem::CommandManager
    @config_file_class = Gem::ConfigFile
end

發(fā)現(xiàn)是它其實(shí) Gem::CommandManager 類,接著查看一下 CommandManager 的 run 方法實(shí)現(xiàn),在文件 command_manager.rb 中 ,有以下的實(shí)現(xiàn)內(nèi)容:

  ##
  # Run the command specified by +args+.

  def run(args, build_args=nil)
    process_args(args, build_args)
  # 異常處理
  rescue StandardError, Timeout::Error => ex
    if ex.respond_to?(:detailed_message)
      msg = ex.detailed_message(highlight: false).sub(/\A(.*?)(?: \(.+?\))/) { $1 }
    else
      msg = ex.message
    end
    alert_error clean_text("While executing gem ... (#{ex.class})\n    #{msg}")
    ui.backtrace ex

    terminate_interaction(1)
  rescue Interrupt
    alert_error clean_text("Interrupted")
    terminate_interaction(1)
  end


  def process_args(args, build_args=nil)
	# 空參數(shù)退出執(zhí)行
    if args.empty?
      say Gem::Command::HELP
      terminate_interaction 1
    end
	# 判斷第一個(gè)參數(shù)
    case args.first
    when "-h", "--help" then
      say Gem::Command::HELP
      terminate_interaction 0
    when "-v", "--version" then
      say Gem::VERSION
      terminate_interaction 0
    when "-C" then
      args.shift
      start_point = args.shift
      if Dir.exist?(start_point)
        Dir.chdir(start_point) { invoke_command(args, build_args) }
      else
        alert_error clean_text("#{start_point} isn't a directory.")
        terminate_interaction 1
      end
    when /^-/ then
      alert_error clean_text("Invalid option: #{args.first}. See 'gem --help'.")
      terminate_interaction 1
    else
	  # 執(zhí)行命令
      invoke_command(args, build_args)
    end
  end
  
  def invoke_command(args, build_args)
    cmd_name = args.shift.downcase
    # 查找指令,并獲取繼承自 Gem::Commands的實(shí)體子類(實(shí)現(xiàn)了excute抽象方法)
    cmd = find_command cmd_name
    cmd.deprecation_warning if cmd.deprecated?
    # 執(zhí)行 invoke_with_build_args 方法(該方法來自基類 Gem::Commands)
    cmd.invoke_with_build_args args, build_args
  end

  def find_command(cmd_name)
    cmd_name = find_alias_command cmd_name
    possibilities = find_command_possibilities cmd_name
    if possibilities.size > 1
      raise Gem::CommandLineError,
            "Ambiguous command #{cmd_name} matches [#{possibilities.join(", ")}]"
    elsif possibilities.empty?
      raise Gem::UnknownCommandError.new(cmd_name)
    end
	# 這里的[] 是方法調(diào)用,定義在下面
    self[possibilities.first]
  end
  ##
  # Returns a Command instance for +command_name+
  def [](command_name)
    command_name = command_name.intern
    return nil if @commands[command_name].nil?
    # 調(diào)用 `load_and_instantiate` 方法來完成這個(gè)過程,并將返回的對(duì)象存儲(chǔ)到 `@commands` 哈希表中,這里 ||= 是默認(rèn)值內(nèi)容,類似于OC中的?:
    @commands[command_name] ||= load_and_instantiate(command_name)
  end

  # 命令分發(fā)選擇以及動(dòng)態(tài)實(shí)例
  def load_and_instantiate(command_name)
    command_name = command_name.to_s
    const_name = command_name.capitalize.gsub(/_(.)/) { $1.upcase } << "Command"
    load_error = nil

    begin
      begin
        require "rubygems/commands/#{command_name}_command"
      rescue LoadError => e
        load_error = e
      end
      # 通過 Gem::Commands 獲取注冊(cè)的變量
      Gem::Commands.const_get(const_name).new
    rescue StandardError => e
      e = load_error if load_error
      alert_error clean_text("Loading command: #{command_name} (#{e.class})\n\t#{e}")
      ui.backtrace e
    end
  end

通過以上的源碼,可以發(fā)現(xiàn)命令的執(zhí)行,通過調(diào)用 process_args 執(zhí)行,然后在 process_args 方法中進(jìn)行判斷命令參數(shù),接著通過 invoke_command 來執(zhí)行命令。在 invoke_command 內(nèi)部,首先通過find_command 查找命令,這里find_command 主要負(fù)責(zé)查找命令相關(guān)的執(zhí)行對(duì)象,需要注意的地方在以下這句:

@commands[command_name] ||= load_and_instantiate(command_name)

通過以上的操作,返回當(dāng)前命令執(zhí)行的實(shí)體對(duì)象,而對(duì)應(yīng)的腳本匹配又是如何實(shí)現(xiàn)的呢(比如輸入的命令是 gem install 命令)?這里的 load_and_instantiate(command_name) 的方法其實(shí)就是查找實(shí)體的具體操作,在實(shí)現(xiàn)中通過以下的語句來獲取最終的常量的命令指令實(shí)體:

Gem::Commands.const_get(const_name).new

上面的語句是通過 Gem::Commands 查找類中的常量,這里的常量其實(shí)就是對(duì)應(yīng)gem相關(guān)的一個(gè)個(gè)指令,在gem中聲明了很多命令的常量,他們繼承自 Gem::Command 基類,同時(shí)實(shí)現(xiàn)了抽象方法 execute,這一點(diǎn)很重要。比如在 install_command.rb 中定義了命令 gem install 的具體的實(shí)現(xiàn):

  def execute
    if options.include? :gemdeps
      install_from_gemdeps
      return # not reached
    end

    @installed_specs = []

    ENV.delete "GEM_PATH" if options[:install_dir].nil?

    check_install_dir
    check_version

    load_hooks

    exit_code = install_gems

    show_installed

    say update_suggestion if eglible_for_update?

    terminate_interaction exit_code
  end

invoke_command 方法中,最終通過 invoke_with_build_args 來最終執(zhí)行命令,該方法定義Gem::Command中,在 command.rb 文件中,可以看到內(nèi)容如下:

  def invoke_with_build_args(args, build_args)
    handle_options args
    options[:build_args] = build_args

    if options[:silent]
      old_ui = ui
      self.ui = ui = Gem::SilentUI.new
    end

    if options[:help]
      show_help
    elsif @when_invoked
      @when_invoked.call options
    else
      execute
    end
  ensure
    if ui
      self.ui = old_ui
      ui.close
    end
  end
  # 子類實(shí)現(xiàn)該抽象完成命令的具體實(shí)現(xiàn)
  def execute
    raise Gem::Exception, "generic command has no actions"
  end

可以看出來,最終基類中的 invoke_with_build_args 中調(diào)用了抽象方法 execute 來完成命令的運(yùn)行調(diào)用。在rubyGems里面聲明了很多變量,這些變量在 CommandManager 中通過 run 方法進(jìn)行命令常量實(shí)體的查找,最終通過調(diào)用繼承自 Gem:Command 子類的 execute 完成相關(guān)指令的執(zhí)行。在rubyGems中可以看到很多變量,一個(gè)變量對(duì)應(yīng)一個(gè)命令,如下所示:

到這里,我們基本可以知道整個(gè)gem命令的查找到調(diào)用的整個(gè)流程。那么 gem install 的過程中又是如何自動(dòng)生成并注冊(cè)相關(guān)的gem命令到系統(tǒng)環(huán)境變量中的呢?基于上面的命令查找調(diào)用流程,其實(shí)只需要在 install_command.rb 中查看 execute 具體的實(shí)現(xiàn)就清楚了,如下:

def execute
    if options.include? :gemdeps
      install_from_gemdeps
      return # not reached
    end

    @installed_specs = []

    ENV.delete "GEM_PATH" if options[:install_dir].nil?

    check_install_dir
    check_version

    load_hooks

    exit_code = install_gems

    show_installed

    say update_suggestion if eglible_for_update?

    terminate_interaction exit_code
  end

  def install_from_gemdeps # :nodoc:
    require_relative "../request_set"
    rs = Gem::RequestSet.new

    specs = rs.install_from_gemdeps options do |req, inst|
      s = req.full_spec

      if inst
        say "Installing #{s.name} (#{s.version})"
      else
        say "Using #{s.name} (#{s.version})"
      end
    end

    @installed_specs = specs

    terminate_interaction
  end
def install_gem(name, version) # :nodoc:
    return if options[:conservative] &&
              !Gem::Dependency.new(name, version).matching_specs.empty?

    req = Gem::Requirement.create(version)

    dinst = Gem::DependencyInstaller.new options

    request_set = dinst.resolve_dependencies name, req

    if options[:explain]
      say "Gems to install:"

      request_set.sorted_requests.each do |activation_request|
        say "  #{activation_request.full_name}"
      end
    else
      @installed_specs.concat request_set.install options
    end

    show_install_errors dinst.errors
  end

  def install_gems # :nodoc:
    exit_code = 0

    get_all_gem_names_and_versions.each do |gem_name, gem_version|
      gem_version ||= options[:version]
      domain = options[:domain]
      domain = :local unless options[:suggest_alternate]
      suppress_suggestions = (domain == :local)

      begin
        install_gem gem_name, gem_version
      rescue Gem::InstallError => e
        alert_error "Error installing #{gem_name}:\n\t#{e.message}"
        exit_code |= 1
      rescue Gem::GemNotFoundException => e
        show_lookup_failure e.name, e.version, e.errors, suppress_suggestions

        exit_code |= 2
      rescue Gem::UnsatisfiableDependencyError => e
        show_lookup_failure e.name, e.version, e.errors, suppress_suggestions,
                            "'#{gem_name}' (#{gem_version})"

        exit_code |= 2
      end
    end

    exit_code
  end



可以看出,最終通過request_set.install 來完成最終的gem安裝,而request_setGem::RequestSet 的實(shí)例對(duì)象,接著在 request_set.rb 中查看相關(guān)的實(shí)現(xiàn):

##
  # Installs gems for this RequestSet using the Gem::Installer +options+.
  #
  # If a +block+ is given an activation +request+ and +installer+ are yielded.
  # The +installer+ will be +nil+ if a gem matching the request was already
  # installed.

  def install(options, &block) # :yields: request, installer
    if dir = options[:install_dir]
      requests = install_into dir, false, options, &block
      return requests
    end

    @prerelease = options[:prerelease]

    requests = []
    # 創(chuàng)建下載隊(duì)列
    download_queue = Thread::Queue.new

    # Create a thread-safe list of gems to download
    sorted_requests.each do |req|
      # 存儲(chǔ)下載實(shí)例
      download_queue << req
    end

    # Create N threads in a pool, have them download all the gems
    threads = Array.new(Gem.configuration.concurrent_downloads) do
      # When a thread pops this item, it knows to stop running. The symbol
      # is queued here so that there will be one symbol per thread.
      download_queue << :stop
	  # 創(chuàng)建線程并執(zhí)行下載
      Thread.new do
        # The pop method will block waiting for items, so the only way
        # to stop a thread from running is to provide a final item that
        # means the thread should stop.
        while req = download_queue.pop
          break if req == :stop
          req.spec.download options unless req.installed?
        end
      end
    end

    # 等待所有線程都執(zhí)行完畢,也就是gem下載完成
    threads.each(&:value)

    # 開始安裝已經(jīng)下載的gem
    sorted_requests.each do |req|
      if req.installed?
        req.spec.spec.build_extensions

        if @always_install.none? {|spec| spec == req.spec.spec }
          yield req, nil if block_given?
          next
        end
      end

      spec =
        begin
          req.spec.install options do |installer|
            yield req, installer if block_given?
          end
        rescue Gem::RuntimeRequirementNotMetError => e
          suggestion = "There are no versions of #{req.request} compatible with your Ruby & RubyGems"
          suggestion += ". Maybe try installing an older version of the gem you're looking for?" unless @always_install.include?(req.spec.spec)
          e.suggestion = suggestion
          raise
        end

      requests << spec
    end

    return requests if options[:gemdeps]

    install_hooks requests, options

    requests
  end

可以發(fā)現(xiàn),整個(gè)過程先是執(zhí)行完被加在隊(duì)列中的所有的線程任務(wù),然后通過遍歷下載的實(shí)例對(duì)象,對(duì)下載的gem進(jìn)行安裝,通過 req.sepc.install options 進(jìn)行安裝,這塊的實(shí)現(xiàn)在 specification.rb 中的 Gem::Resolver::Specification 定義如下:

  def install(options = {})
    require_relative "../installer"
	# 獲取下載的gem
    gem = download options
	# 獲取安裝實(shí)例
    installer = Gem::Installer.at gem, options
	# 回調(diào)輸出
    yield installer if block_given?
	# 執(zhí)行安裝
    @spec = installer.install
  end

  def download(options)
    dir = options[:install_dir] || Gem.dir
    Gem.ensure_gem_subdirectories dir
    source.download spec, dir
  end



從上面的源碼可以知道,最終安裝放在了 Gem::Installerinstall 方法中執(zhí)行的。它的執(zhí)行過程如下:

def install
	# 安裝檢查
    pre_install_checks
	# 運(yùn)行執(zhí)行前腳本hook
    run_pre_install_hooks
    # Set loaded_from to ensure extension_dir is correct
    if @options[:install_as_default]
      spec.loaded_from = default_spec_file
    else
      spec.loaded_from = spec_file
    end

    # Completely remove any previous gem files
    FileUtils.rm_rf gem_dir
    FileUtils.rm_rf spec.extension_dir

    dir_mode = options[:dir_mode]
    FileUtils.mkdir_p gem_dir, :mode => dir_mode && 0o755

	# 默認(rèn)設(shè)置安裝
    if @options[:install_as_default]
      extract_bin
      write_default_spec
    else
      extract_files
      build_extensions
      write_build_info_file
      run_post_build_hooks
    end

	# 生成bin目錄可執(zhí)行文件
    generate_bin
    # 生成插件
    generate_plugins

    unless @options[:install_as_default]
      write_spec
      write_cache_file
    end

    File.chmod(dir_mode, gem_dir) if dir_mode

    say spec.post_install_message if options[:post_install_message] && !spec.post_install_message.nil?

    Gem::Specification.add_spec(spec)
	# 運(yùn)行install的hook腳本
    run_post_install_hooks

    spec

這段源碼中,我們清晰的看到在執(zhí)行安裝的整個(gè)過程之后,又通過 generate_bingenerate_plugins 動(dòng)態(tài)生成了兩個(gè)文件,對(duì)于 generate_bin 的生成過程如下:

def generate_bin # :nodoc:
    return if spec.executables.nil? || spec.executables.empty?

    ensure_writable_dir @bin_dir

    spec.executables.each do |filename|
      filename.tap(&Gem::UNTAINT)
      bin_path = File.join gem_dir, spec.bindir, filename
      next unless File.exist? bin_path

      mode = File.stat(bin_path).mode
      dir_mode = options[:prog_mode] || (mode | 0o111)

      unless dir_mode == mode
        require "fileutils"
        FileUtils.chmod dir_mode, bin_path
      end
	  # 檢查是否存在同名文件被復(fù)寫
      check_executable_overwrite filename

      if @wrappers
	    # 生成可執(zhí)行腳本
        generate_bin_script filename, @bin_dir
      else
        # 生成符號(hào)鏈接
        generate_bin_symlink filename, @bin_dir
      end
    end
  end

在經(jīng)過一系列的路徑判斷與寫入環(huán)境判斷之后,通過 generate_bin_script 生成動(dòng)態(tài)可執(zhí)行腳本文件,到這里,是不是對(duì)關(guān)于gem進(jìn)行安裝的時(shí)候動(dòng)態(tài)生成系統(tǒng)可識(shí)別的命令指令有了清晰的認(rèn)識(shí)與解答。其實(shí)本質(zhì)是Ruby在安裝gem之后,會(huì)通過 generate_bin_script 生成可執(zhí)行腳本并動(dòng)態(tài)注入到系統(tǒng)的環(huán)境變量中,進(jìn)而能夠讓系統(tǒng)識(shí)別到gem安裝的相關(guān)指令,為gem的功能觸發(fā)提供入口。以下是generate_bin_script 的實(shí)現(xiàn):

  ##
  # Creates the scripts to run the applications in the gem.
  #--
  # The Windows script is generated in addition to the regular one due to a
  # bug or misfeature in the Windows shell's pipe.  See
  # https://blade.ruby-lang.org/ruby-talk/193379

  def generate_bin_script(filename, bindir)
    bin_script_path = File.join bindir, formatted_program_filename(filename)

    require "fileutils"
    FileUtils.rm_f bin_script_path # prior install may have been --no-wrappers

    File.open bin_script_path, "wb", 0o755 do |file|
      file.print app_script_text(filename)
      file.chmod(options[:prog_mode] || 0o755)
    end

    verbose bin_script_path

    generate_windows_script filename, bindir
  end




關(guān)于腳本具體內(nèi)容的生成,這里就不再細(xì)說了,感興趣的話可以去官方的源碼中的installer.rb 中查看細(xì)節(jié),摘取了主要內(nèi)容如下:

  def app_script_text(bin_file_name)
    # NOTE: that the `load` lines cannot be indented, as old RG versions match
    # against the beginning of the line
    <<-TEXT
#{shebang bin_file_name}
#
# This file was generated by RubyGems.
#
# The application '#{spec.name}' is installed as part of a gem, and
# this file is here to facilitate running it.
#

require 'rubygems'
#{gemdeps_load(spec.name)}
version = "#{Gem::Requirement.default_prerelease}"

str = ARGV.first
if str
  str = str.b[/\\A_(.*)_\\z/, 1]
  if str and Gem::Version.correct?(str)
    #{explicit_version_requirement(spec.name)}
    ARGV.shift
  end
end

if Gem.respond_to?(:activate_bin_path)
load Gem.activate_bin_path('#{spec.name}', '#{bin_file_name}', version)
else
gem #{spec.name.dump}, version
load Gem.bin_path(#{spec.name.dump}, #{bin_file_name.dump}, version)
end
TEXT
  end

  def gemdeps_load(name)
    return "" if name == "bundler"

    <<-TEXT

Gem.use_gemdeps
TEXT
  end

小結(jié)一下:之所以系統(tǒng)能夠識(shí)別我們安裝的gems包命令,本質(zhì)原因是RubyGems在進(jìn)行包安裝的時(shí)候,通過 generate_bin_script 動(dòng)態(tài)的生成了可執(zhí)行的腳本文件,并將其注入到了系統(tǒng)的環(huán)境變量路徑Path中。我們通過系統(tǒng)的環(huán)境變量作為引導(dǎo)入口,再間接的調(diào)取gem安裝包的具體實(shí)現(xiàn),進(jìn)而完成整個(gè)gem的功能調(diào)用。

三、CocoaPods是如何在Ruby的基礎(chǔ)上都做了自己的領(lǐng)域型DSL?

想想日常使用cocoaPods引入三方組件的時(shí)候,通常都在Podfile中進(jìn)行相關(guān)的配置就行了,而在Podfile中的配置規(guī)則其實(shí)就是Cocoapods在Ruby的基礎(chǔ)上提供給開發(fā)者的領(lǐng)域型DSL,該DSL主要針對(duì)與項(xiàng)目的依賴庫管理進(jìn)行領(lǐng)域規(guī)則描述,由CocoaPods的DSL解析器完成規(guī)則解析,最終通過pods的相關(guān)命令來完成整個(gè)項(xiàng)目的庫的日常管理。這么說沒有什么問題,但是Cocoapods的底層邏輯到底是什么?也是接下來想重點(diǎn)探討挖掘的。

繼續(xù)從簡(jiǎn)單 pod install 命令來一探究竟,通過第一節(jié)的源碼分析,我們知道,該命令最終會(huì)轉(zhuǎn)發(fā)到 cocoaPods 源碼下的 install.rb中,直接看它的 run方法,如下:

class Install < Command
···
  def run
    # 是否存在podfile文件
	verify_podfile_exists!
	# 創(chuàng)建installer對(duì)象(installer_for_config定義在基類Command中)
	installer = installer_for_config
	# 更新倉庫
	installer.repo_update = repo_update?(:default => false)
	# 關(guān)閉更新
	installer.update = false
	# 屬性透?jìng)?	installer.deployment = @deployment
	installer.clean_install = @clean_install
	# 執(zhí)行安裝
	installer.install!
  end
  
  def installer_for_config
      Installer.new(config.sandbox, config.podfile, config.lockfile)
  end
···
 end 

執(zhí)行安裝的操作是通過 installer_for_config 方法來完成的,在方法實(shí)現(xiàn)中,實(shí)例了 Installer 對(duì)象,入?yún)?sandbox 、podfile 、lockfile ,而這些入?yún)⒕峭ㄟ^ config 對(duì)象方法獲取,而podfile的獲取過程正是我們想要了解的,所以知道 config 的定義地方至關(guān)重要。在 command.rb 中我發(fā)現(xiàn)有如下的內(nèi)容:

include Config::Mixin

這段代碼引入了 Config::Mixin 類,而他在 Config 中的定義如下:

class Config
···
	module Mixin
	  def config
		Config.instance
	  end
	end
    def self.instance
      @instance ||= new
    end
    def sandbox
      @sandbox ||= Sandbox.new(sandbox_root)
    end
    def podfile
      @podfile ||= Podfile.from_file(podfile_path) if podfile_path
    end
    attr_writer :podfile
    def lockfile
      @lockfile ||= Lockfile.from_file(lockfile_path) if lockfile_path
    end

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

定義了一個(gè)名為Mixin的模塊,其中包含一個(gè)名為config的方法,在該方法中實(shí)例了 Config 對(duì)象。這里定義了剛剛實(shí)例 Installer 的時(shí)候的三個(gè)入?yún)?。重點(diǎn)看一下 podfile,可以看出 podfile 的實(shí)現(xiàn)中通過 Podfile.from_file(podfile_path) 來拿到最終的配置內(nèi)容,那么關(guān)于Podfile 的讀取謎底也就在這個(gè) from_file 方法實(shí)現(xiàn)中了,通過搜索發(fā)現(xiàn)在Cocoapods中的源碼中并沒有該方法的定義,只有以下的內(nèi)容:

require 'cocoapods-core/podfile'

module Pod
 class Podfile 
    autoload :InstallationOptions, 'cocoapods/installer/installation_options'

    # @return [Pod::Installer::InstallationOptions] the installation options specified in the Podfile
    #
    def installation_options
      @installation_options ||= Pod::Installer::InstallationOptions.from_podfile(self)
    end
  end
end

可以看到這里的class Podfile 定義的Podfile 的原始類,同時(shí)發(fā)現(xiàn)源碼中引用了 cocoapods-core/podfile 文件,這里應(yīng)該能猜想到,關(guān)于 from_file 的實(shí)現(xiàn)應(yīng)該是在cocoapods-core/podfile 中完成的。這個(gè)資源引入是 Cocoapods的一個(gè)核心庫的組件,通過對(duì)核心庫 cocoapods-core,進(jìn)行檢索,發(fā)現(xiàn)在文件 podfile.rb 中有如下的內(nèi)容:

module Pod

  class Podfile
    # @!group DSL support

    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
	  # 判斷擴(kuò)展名文件
      case path.extname
      when '', '.podfile', '.rb'
	    # 按照Ruby格式解析
        Podfile.from_ruby(path)
      when '.yaml'
        # 按照yaml格式進(jìn)行解析
        Podfile.from_yaml(path)
      else
	    # 格式異常拋出
        raise Informative, "Unsupported Podfile format `#{path}`."
      end
    end
    
   def self.from_ruby(path, contents = nil)
	  # 以u(píng)tf-8格式打開文件內(nèi)容
      contents ||= File.open(path, 'r:utf-8', &:read)

      # Work around for Rubinius incomplete encoding in 1.9 mode
      if contents.respond_to?(:encoding) && contents.encoding.name != 'UTF-8'
        contents.encode!('UTF-8')
      end

      if contents.tr!('“”‘’?', %(""'''))
        # Changes have been made
        CoreUI.warn "Smart quotes were detected and ignored in your #{path.basename}. " \
                    'To avoid issues in the future, you should not use ' \
                    'TextEdit for editing it. If you are not using TextEdit, ' \
                    'you should turn off smart quotes in your editor of choice.'
      end

	  # 實(shí)例podfile對(duì)象
      podfile = Podfile.new(path) do
        # rubocop:disable Lint/RescueException
        begin
          # 執(zhí)行podFile內(nèi)容(執(zhí)行之前會(huì)先執(zhí)行Podfile初始化Block回調(diào)前的內(nèi)容)
          eval(contents, nil, path.to_s)
          # DSL的異常拋出
        rescue Exception => e
          message = "Invalid `#{path.basename}` file: #{e.message}"
          raise DSLError.new(message, path, e, contents)
        end
        # rubocop:enable Lint/RescueException
      end
      podfile
    end
    
    def self.from_yaml(path)
      string = File.open(path, 'r:utf-8', &:read)
      # Work around for Rubinius incomplete encoding in 1.9 mode
      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 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

從上面的源碼可以知道,整個(gè)的 Podfile 的讀取流程如下: 1. 判斷路徑是否合法,不合法拋出異常 2. 判斷擴(kuò)展名類型,如果是 '', '.podfile', '.rb' 擴(kuò)展按照 ruby 語法規(guī)則解析,如果是yaml則按照 yaml 文件格式解析,以上兩者如果都不是,則拋出格式解析異常 3. 如果解析按照 Ruby 格式解析的話過程如下:

? 按照utf-8格式讀取 Podfile 文件內(nèi)容,并存儲(chǔ)到 contents

? 內(nèi)容符號(hào)容錯(cuò)處理,主要涉及" “”‘’?" 等 符號(hào),同時(shí)輸出警告信息

? 實(shí)例 Podfile 對(duì)象,同時(shí)在實(shí)例過程中初始化 TargetDefinition 對(duì)象并配置默認(rèn)的Target 信息

? 最終通過 eval(contents, nil, path.to_s) 方法執(zhí)行 Podfile 文件內(nèi)容完成配置記錄

這里或許有一個(gè)疑問:Podfile里面定義了 Cocoapods 自己的一套DSL語法,那么執(zhí)行過程中是如何解析DSL語法的呢?上面的源碼文件中,如果仔細(xì)查看的話,會(huì)發(fā)現(xiàn)有下面這一行內(nèi)容:

include Pod::Podfile::DSL

不錯(cuò),這就是DSL解析的本體,其實(shí)你可以將DSL語法理解為基于Ruby定義的一系列的領(lǐng)域型方法,DSL的解析的過程本質(zhì)是定義的方法執(zhí)行的過程。在Cocoapods中定義了很多DSL語法,定義與實(shí)現(xiàn)均放在了 cocoapods-core 這個(gè)核心組件中,比如在dsl.rb 文件中的以下關(guān)于PodfileDSL定義(摘取部分):

module Pod
  class Podfile
	  module DSL
	  
      def install!(installation_method, options = {})
        unless current_target_definition.root?
          raise Informative, 'The installation method can only be set at the root level of the Podfile.'
        end

        set_hash_value('installation_method', 'name' => installation_method, 'options' => options)
      end
      
      def pod(name = nil, *requirements)
        unless name
          raise StandardError, 'A dependency requires a name.'
        end

        current_target_definition.store_pod(name, *requirements)
      end

      def podspec(options = nil)
        current_target_definition.store_podspec(options)
      end
      
      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
      
      def inherit!(inheritance)
        current_target_definition.inheritance = inheritance
      end
      
      def platform(name, target = nil)
        # Support for deprecated options parameter
        target = target[:deployment_target] if target.is_a?(Hash)
        current_target_definition.set_platform!(name, target)
      end
      
      def project(path, build_configurations = {})
        current_target_definition.user_project_path = path
        current_target_definition.build_configurations = build_configurations
      end
      
      def xcodeproj(*args)
        CoreUI.warn '`xcodeproj` was renamed to `project`. Please update your Podfile accordingly.'
        project(*args)
      end
  .......
  end
end

看完 DSL的定義實(shí)現(xiàn)是不是有種熟悉的味道,對(duì)于使用Cocoapods的使用者而言,在沒有接觸Ruby的情況下,依舊能夠通過對(duì)Podfile的簡(jiǎn)單配置來實(shí)現(xiàn)三方庫的管理依賴,不僅使用的學(xué)習(xí)成本低,而且能夠很容易的上手,之所以能夠這么便捷,就體現(xiàn)出了DSL的魅力所在。

對(duì)于**領(lǐng)域型語言**的方案選用在很多不同的業(yè)務(wù)領(lǐng)域中都有了相關(guān)的應(yīng)用,它對(duì)特定的**業(yè)務(wù)領(lǐng)域場(chǎng)景**能夠提供**高效簡(jiǎn)潔**的實(shí)現(xiàn)方案,對(duì)使用者友好的同時(shí),也能提供高質(zhì)量的領(lǐng)域能力。**cocoapods**就是借助Ruby強(qiáng)大的面向?qū)ο蟮哪_本能力完成**Cocoa庫**管理的實(shí)現(xiàn),有種偷梁換柱的感覺,為使用者提供了領(lǐng)域性語言,讓其更簡(jiǎn)單更高效,尤其是使用者并沒有感知到其本質(zhì)是**Ruby**記得一開始使用Cocoapods的時(shí)候,曾經(jīng)一度以為它是一種新的語言,現(xiàn)在看來都是Cocoapods的DSL所給我們的錯(cuò)覺,畢竟使用起來實(shí)在是太香了。

作者:京東零售 李臣臣

來源:京東云開發(fā)者社區(qū) 轉(zhuǎn)載請(qǐng)注明來源文章來源地址http://www.zghlxwxcb.cn/news/detail-709980.html

到了這里,關(guān)于CocoaPods 在iOS開發(fā)中養(yǎng)活了這么多項(xiàng)目,它到底是個(gè)啥?的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • AIGC到底是個(gè)啥? AI和AIGC的區(qū)別有哪些

    AIGC到底是個(gè)啥? AI和AIGC的區(qū)別有哪些

    ? 說來說去,到底什么是AIGC呢?AIGC是人工智能生成內(nèi)容的縮寫,是一種基于生成對(duì)抗網(wǎng)絡(luò)(GAN)、大型預(yù)訓(xùn)練模型等人工智能技術(shù)的方法,通過對(duì)已有數(shù)據(jù)進(jìn)行學(xué)習(xí)和模式識(shí)別,以適當(dāng)?shù)姆夯芰ι上嚓P(guān)內(nèi)容的技術(shù),AIGC的應(yīng)用領(lǐng)域有很多例如,AIGC可以用于自動(dòng)生成新聞、

    2024年02月08日
    瀏覽(16)
  • 越來越火的Serverless(無服務(wù)器計(jì)算),到底是個(gè)啥?

    越來越火的Serverless(無服務(wù)器計(jì)算),到底是個(gè)啥?

    今天這篇文章,我們來聊一個(gè)云計(jì)算領(lǐng)域的熱門概念——Serverless。 到底什么是Serverless? 英語好的童鞋,可能一眼就看出來了,Serverless是由Server和less兩個(gè)詞根組成的詞。從字面上理解,就是“無服務(wù)器”。 行業(yè)通常所說的Serverless,主要是指 “無服務(wù)器計(jì)算(Serverless Comp

    2024年02月03日
    瀏覽(22)
  • 云原生到底是個(gè)啥玩意?從云端降臨的超級(jí)技術(shù):云原生

    云原生到底是個(gè)啥玩意?從云端降臨的超級(jí)技術(shù):云原生

    目錄 福利:文末分享云原生相關(guān)全套資料哦 一、云計(jì)算?云原生? 二、云原生帶來什么好處 7個(gè)字:隔離、彈性、自動(dòng)化。 三、云原生的基礎(chǔ)知識(shí) 四、云原生中最重要的概念 1、虛機(jī) 2、容器 3、容器編排 4、VPC 5、微服務(wù)(Microservices) 6、服務(wù)網(wǎng)格(Service Mesh) 7、無服務(wù)器

    2024年02月08日
    瀏覽(20)
  • .NET是個(gè)啥_拓展

    差不多一年前寫過一篇博客——.NET是個(gè)啥。 那篇博客基本上就是按照官方文檔的翻譯,并加入一些自己的理解和吐槽來寫的?,F(xiàn)在回過去看呢,雖然內(nèi)容全面,但是停留在表面。(相當(dāng)于回答了它是什么,它包含了什么) 什么意思呢? 通過那篇博客,可以知道.NET是微軟的

    2024年02月10日
    瀏覽(19)
  • ffmpeg anull 是個(gè)啥?

    1. ffmpeg anull 是個(gè)啥? ---------------------------------------- author: hjjdebug date:?? 2023年 07月 13日 星期四 17:59:47 CST ---------------------------------------- 當(dāng)然是ffmpeg audio filter了, $ ffmpeg -h filter=anull 給出了如下信息: Filter anull ? Pass the source unchanged to the output. ??? Inputs: ?????? #0: default (au

    2024年02月15日
    瀏覽(18)
  • OCP3.0是個(gè)啥?

    OCP3.0是個(gè)啥?

    1、OCP NIC 3.0規(guī)范是OCP Mezz 2.0 設(shè)計(jì)規(guī)范的升級(jí)。 2、OCP NIC 3.0規(guī)范支持兩種基本卡尺寸:小尺寸Small Form Factor(SFF)和大尺寸Large Form Factor(LFF)。 3、SFF最多支持16 lane PCIe ,而LFF最多支持32 lane PCIe。 與OCP Mezz 2.0設(shè)計(jì)規(guī)范相比,更新的OCP網(wǎng)卡3.0規(guī)范為網(wǎng)卡和系統(tǒng)供應(yīng)商提供更廣闊的解決

    2024年02月05日
    瀏覽(16)
  • 【C#】微軟的Roslyn 是個(gè)啥?

    【C#】微軟的Roslyn 是個(gè)啥?

    ????????Roslyn 是微軟重寫的C#編譯器并開源。 ????????Roslyn ?是 C# 和 Visual Basic.NET 開源編譯器的代號(hào)。以下是它如何在過去十年企業(yè)Microsoft的最黑暗中開始,并成為所有C#(和VB)的開源,跨平臺(tái),公共語言引擎,我將在本文的其余部分將其視為給定的)。 ???????

    2024年02月15日
    瀏覽(17)
  • 了解 Langchain?是個(gè)啥?:第 1 部分

    ????????在日常生活中,我們主要致力于構(gòu)建端到端的應(yīng)用程序。我們可以使用許多自動(dòng) ML 平臺(tái)和 CI/CD 管道來自動(dòng)化 ml 管道。我們還有像Roboflow和Andrew N.G.的登陸AI這樣的工具來自動(dòng)化或創(chuàng)建端到端的計(jì)算機(jī)視覺應(yīng)用程序。 ????????如果我們想在OpenAI或擁抱臉的幫助下

    2024年02月13日
    瀏覽(26)
  • 液體神經(jīng)網(wǎng)絡(luò):LNN是個(gè)啥概念?

    液體神經(jīng)網(wǎng)絡(luò):LNN是個(gè)啥概念?

    ????????在在人工智能領(lǐng)域,神經(jīng)網(wǎng)絡(luò)已被證明是解決復(fù)雜問題的非常強(qiáng)大的工具。多年來,研究人員不斷尋求創(chuàng)新方法來提高其性能并擴(kuò)展其能力。其中一種方法是液體神經(jīng)網(wǎng)絡(luò)(LNN)的概念,這是一個(gè)利用動(dòng)態(tài)計(jì)算功能的迷人框架。在本文中,我們將深入研究 LNN 的世

    2024年02月13日
    瀏覽(16)
  • 大語言模型:LLM的概念是個(gè)啥?

    ? ?? ????????大語言模型(維基:LLM-? large language model )是以大尺寸為特征的語言模型。它們的規(guī)模是由人工智能加速器實(shí)現(xiàn)的,人工智能加速器能夠處理大量文本數(shù)據(jù),這些數(shù)據(jù)大部分是從互聯(lián)網(wǎng)上抓取的。 [1]所構(gòu)建的人工神經(jīng)網(wǎng)絡(luò)可以包含數(shù)千萬到數(shù)十億的權(quán)重,

    2024年02月13日
    瀏覽(22)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包