CocoaPods 在iOS开发中养活了这么多项目,它到底是个啥? ...

打印 上一主题 下一主题

主题 778|帖子 778|积分 2334

对于iOS开发者而言,CocoaPods并不陌生,通过pod相关的命令操作,就可以很方便的将项目中用到的三方依赖库资源集成到项目环境中,大大的提升了开发的效率。CocoaPods作为iOS项目的包管理工具,它在命令行背后做了什么操作?而又是通过什么样的方式将命令指令声明出来供我们使用的?这些实现的背后底层逻辑是什么?都是本文想要探讨挖掘的。
一、Ruby是如何让系统能够识别已经安装的Pods指令的?

我们都知道在使用CocoaPods管理项目三方库之前,需要安装Ruby环境,同时基于Ruby的包管理工具gem再去安装CocoaPods。通过安装过程可以看出来,CocoaPods本质就是Ruby的一个gem包。而安装Cocoapods的时候,使用了以下的安装命令:
  1. sudo gem install cocoapods
复制代码
安装完成之后,就可以使用基于Cocoapods的 pod xxxx 相关命令了。gem install xxx 到底做了什么也能让 Terminal 正常的识别 pod 命令?gem的工作原理又是什么?了解这些之前,可以先看一下 RubyGems 的环境配置,通过以下的命令:
  1. gem environment
复制代码



通过以上的命令,可以看到Ruby的版本信息,RubyGem的版本,以及gems包安装的路径,进入安装路径 /Library/Ruby/Gems/2.6.0 后,我们能看到当前的Ruby环境下所安装的扩展包,这里能看到我们熟悉的Cocoapods相关的功能包。除了安装包路径之外,还有一个 EXECUTABLE DIRECTORY 执行目录 /usr/local/bin,可以看到拥有可执行权限的pod文件,如下:

预览一下pod文件内容:
  1. #!/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/bin/ruby
  2. #
  3. # This file was generated by RubyGems.
  4. #
  5. # The application 'cocoapods' is installed as part of a gem, and
  6. # this file is here to facilitate running it.
  7. #
  8. require 'rubygems'
  9. version = ">= 0.a"
  10. str = ARGV.first
  11. if str
  12.   str = str.b[/\A_(.*)_\z/, 1]
  13.   if str and Gem::Version.correct?(str)
  14.     version = str
  15.     ARGV.shift
  16.   end
  17. end
  18. if Gem.respond_to?(:activate_bin_path)
  19. load Gem.activate_bin_path('cocoapods', 'pod', version)
  20. else
  21. gem "cocoapods", version
  22. load Gem.bin_path("cocoapods", "pod", version)
  23. end
复制代码
根据文件注释内容可以发现,当前的可执行文件是 RubyGems 在安装 Cocoapods 的时候自动生成的,同时会将当前的执行文件放到系统的环境变量路径中,也即存放到了 /usr/local/bin 中了,这也就解释了为什么我们通过gem安装cocoapods之后,就立马能够识别pod可执行环境了。
虽然能够识别pod可执行文件,但是具体的命令参数是如何进行识别与实现呢?继续看以上的pod的文件源码,会发现最终都指向了 Gem 的 activate_bin_path 与 bin_path 方法,为了搞清楚Gem到底做了什么,在官方的RubyGems源码的rubygems.rb 文件中找到了两个方法的相关定义与实现,摘取了主要的几个方法实现,内容如下:
  1.   ##
  2.   # Find the full path to the executable for gem +name+.  If the +exec_name+
  3.   # is not given, an exception will be raised, otherwise the
  4.   # specified executable's path is returned.  +requirements+ allows
  5.   # you to specify specific gem versions.
  6.   #
  7.   # A side effect of this method is that it will activate the gem that
  8.   # contains the executable.
  9.   #
  10.   # This method should *only* be used in bin stub files.
  11.   def self.activate_bin_path(name, exec_name = nil, *requirements) # :nodoc:
  12.     spec = find_spec_for_exe name, exec_name, requirements
  13.     Gem::LOADED_SPECS_MUTEX.synchronize do
  14.       spec.activate
  15.       finish_resolve
  16.     end
  17.     spec.bin_file exec_name
  18.   end
  19.   def self.find_spec_for_exe(name, exec_name, requirements)
  20.         #如果没有提供可执行文件的名称,则抛出异常
  21.     raise ArgumentError, "you must supply exec_name" unless exec_name
  22.     # 创建一个Dependency对象
  23.     dep = Gem::Dependency.new name, requirements
  24.     # 获取已经加载的gem
  25.     loaded = Gem.loaded_specs[name]
  26.     # 存在直接返回
  27.     return loaded if loaded && dep.matches_spec?(loaded)
  28.     # 查找复合条件的gem配置
  29.     specs = dep.matching_specs(true)
  30.     specs = specs.find_all do |spec|
  31.           # 匹配exec_name 执行名字,如果匹配结束查找
  32.       spec.executables.include? exec_name
  33.     end if exec_name
  34.         # 如果没有找到符合条件的gem,抛出异常
  35.     unless spec = specs.first
  36.       msg = "can't find gem #{dep} with executable #{exec_name}"
  37.       raise Gem::GemNotFoundException, msg
  38.     end
  39.         #返回结果
  40.     spec
  41.   end
  42.   private_class_method :find_spec_for_exe
  43.   ##
  44.   # Find the full path to the executable for gem +name+.  If the +exec_name+
  45.   # is not given, an exception will be raised, otherwise the
  46.   # specified executable's path is returned.  +requirements+ allows
  47.   # you to specify specific gem versions.
  48.   def self.bin_path(name, exec_name = nil, *requirements)
  49.     requirements = Gem::Requirement.default if
  50.       requirements.empty?
  51.     # 通过exec_name 查找gem中可执行文件
  52.     find_spec_for_exe(name, exec_name, requirements).bin_file exec_name
  53.   end
  54.   
  55. class Gem::Dependency
  56.   def matching_specs(platform_only = false)
  57.     env_req = Gem.env_requirement(name)
  58.     matches = Gem::Specification.stubs_for(name).find_all do |spec|
  59.       requirement.satisfied_by?(spec.version) && env_req.satisfied_by?(spec.version)
  60.     end.map(&:to_spec)
  61.     if prioritizes_bundler?
  62.       require_relative "bundler_version_finder"
  63.       Gem::BundlerVersionFinder.prioritize!(matches)
  64.     end
  65.     if platform_only
  66.       matches.reject! do |spec|
  67.         spec.nil? || !Gem::Platform.match_spec?(spec)
  68.       end
  69.     end
  70.     matches
  71.   end
  72. end
  73. class Gem::Specification < Gem::BasicSpecification
  74.   def self.stubs_for(name)
  75.     if @@stubs
  76.       @@stubs_by_name[name] || []
  77.     else
  78.       @@stubs_by_name[name] ||= stubs_for_pattern("#{name}-*.gemspec").select do |s|
  79.         s.name == name
  80.       end
  81.     end
  82.   end
  83. end
复制代码
通过当前的实现可以看出在两个方法实现中,通过 find_spec_for_exe 方法依据名称name查找sepc对象,匹配成功之后返回sepc对象,最终通过spec对象中的bin_file方法来进行执行相关的命令。以下为gems安装的配置目录集合:

注:bin_file 方法的实现方式取决于 gem 包的类型和所使用的操作系统。在大多数情况下,它会根据操作系统的不同,使用不同的查找算法来确定二进制文件的路径。例如,在Windows上,它会搜索 gem包的 bin 目录,而在 Unix 上,它会搜索 gem 包的 bin目录和 PATH 环境变量中的路径。
通过当前的实现可以看出在两个方法实现中,find_spec_for_exe 方法会遍历所有已安装的 gem 包,查找其中包含指定可执行文件的 gem 包。如果找到了匹配的 gem 包,则会返回该 gem 包的 Gem::Specification 对象,并调用其 bin_file 方法获取二进制文件路径。而 bin_file 是在 Gem::Specification 类中定义的。它是一个实例方法,用于查找与指定的可执行文件 exec_name 相关联的 gem 包的二进制文件路径,定义实现如下:
  1.   def bin_dir
  2.     @bin_dir ||= File.join gem_dir, bindir
  3.   end
  4.   ##
  5.   # Returns the full path to installed gem's bin directory.
  6.   #
  7.   # NOTE: do not confuse this with +bindir+, which is just 'bin', not
  8.   # a full path.
  9.   def bin_file(name)
  10.     File.join bin_dir, name
  11.   end
复制代码
到这里,可以看出,pod命令本质是执行了RubyGems 的 find_spec_for_exe 方法,用来查找并执行gems安装目录下的bin目录,也即是 /Library/Ruby/Gems/2.6.0 目录下的gem包下的bin目录。而针对于pod的gem包,如下所示:

至此,可以发现,由系统执行环境 /usr/local/bin 中的可执行文件 pod 引导触发,Ruby通过 Gem.bin_path("cocoapods", "pod", version) 与 Gem.activate_bin_path('cocoapods', 'pod', version) 进行转发,再到gems包安装目录的gem查找方法 find_spec_for_exe,最终转到gems安装包下的bin目录的执行文件进行命令的最终执行,流程大致如下:

而对于pod的命令又是如何进行识别区分的呢?刚刚的分析可以看出对于gems安装包的bin下的执行文件才是最终的执行内容,打开cocoapod的bin目录下的pod可执行文件,如下:
  1. #!/usr/bin/env ruby
  2. if Encoding.default_external != Encoding::UTF_8
  3.   if ARGV.include? '--no-ansi'
  4.     STDERR.puts <<-DOC
  5.     WARNING: CocoaPods requires your terminal to be using UTF-8 encoding.
  6.     Consider adding the following to ~/.profile:
  7.     export LANG=en_US.UTF-8
  8.     DOC
  9.   else
  10.     STDERR.puts <<-DOC
  11.     \e[33mWARNING: CocoaPods requires your terminal to be using UTF-8 encoding.
  12.     Consider adding the following to ~/.profile:
  13.     export LANG=en_US.UTF-8
  14.     \e[0m
  15.     DOC
  16.   end
  17. end
  18. if $PROGRAM_NAME == __FILE__ && !ENV['COCOAPODS_NO_BUNDLER']
  19.   ENV['BUNDLE_GEMFILE'] = File.expand_path('../../Gemfile', __FILE__)
  20.   require 'rubygems'
  21.   require 'bundler/setup'
  22.   $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
  23. elsif ENV['COCOAPODS_NO_BUNDLER']
  24.   require 'rubygems'
  25.   gem 'cocoapods'
  26. end
  27. STDOUT.sync = true if ENV['CP_STDOUT_SYNC'] == 'TRUE'
  28. require 'cocoapods'
  29. # 环境变量判断是否配置了profile_filename,如果配置了按照配置内容生成
  30. if profile_filename = ENV['COCOAPODS_PROFILE']
  31.   require 'ruby-prof'
  32.   reporter =
  33.     case (profile_extname = File.extname(profile_filename))
  34.     when '.txt'
  35.       RubyProf::FlatPrinterWithLineNumbers
  36.     when '.html'
  37.       RubyProf::GraphHtmlPrinter
  38.     when '.callgrind'
  39.       RubyProf::CallTreePrinter
  40.     else
  41.       raise "Unknown profiler format indicated by extension: #{profile_extname}"
  42.     end
  43.   File.open(profile_filename, 'w') do |io|
  44.     reporter.new(RubyProf.profile { Pod::Command.run(ARGV) }).print(io)
  45.   end
  46. else
  47.   Pod::Command.run(ARGV)
  48. end
复制代码
可以发现最终通过执行 Gem::GemRunner.new.run args 来完成安装,显然安装的过程就在 Gem::GemRunner 类中。依旧查看RubyGems的源码,在 gem_runner.rb 中,有着以下的定义:
  1.   class Command < CLAide::Command
  2.     def self.run(argv)
  3.       ensure_not_root_or_allowed! argv
  4.       verify_minimum_git_version!
  5.       verify_xcode_license_approved!
  6.       super(argv)
  7.     ensure
  8.       UI.print_warnings
  9.     end
  10.   end
复制代码
可以看出来命令的执行最终转到了 cmd.run Gem.configuration.args, build_args 的方法调用上,cmd是通过 @command_manager_class 进行装饰的类,找到其装饰的地方如下:
  1.     def initialize(argv)
  2.       argv = ARGV.coerce(argv)
  3.       @verbose = argv.flag?('verbose')
  4.       @ansi_output = argv.flag?('ansi', Command.ansi_output?)
  5.       @argv = argv
  6.       @help_arg = argv.flag?('help')
  7.     end
  8.    
  9.     def self.run(argv = [])
  10.       plugin_prefixes.each do |plugin_prefix|
  11.         PluginManager.load_plugins(plugin_prefix)
  12.       end
  13.       # 转换成ARGV对象
  14.       argv = ARGV.coerce(argv)
  15.       # 处理有效命令行参数
  16.       command = parse(argv)
  17.       ANSI.disabled = !command.ansi_output?
  18.       unless command.handle_root_options(argv)
  19.             # 命令处理
  20.         command.validate!
  21.         # 运行命令(由子类进行继承实现运行)
  22.         command.run
  23.       end
  24.     rescue Object => exception
  25.       handle_exception(command, exception)
  26.     end
  27.     def self.parse(argv)
  28.       argv = ARGV.coerce(argv)
  29.       cmd = argv.arguments.first
  30.       # 命令存在,且子命令存在,进行再次解析
  31.       if cmd && subcommand = find_subcommand(cmd)
  32.             # 移除第一个参数
  33.         argv.shift_argument
  34.         # 解析子命令
  35.         subcommand.parse(argv)
  36.           # 不能执行的命令直接加载默认命令
  37.       elsif abstract_command? && default_subcommand
  38.         load_default_subcommand(argv)
  39.       # 无内容则创建一个comand实例返回
  40.       else
  41.         new(argv)
  42.       end
  43.     end
  44.     # 抽象方法,由其子类进行实现
  45.     def run
  46.       raise 'A subclass should override the `CLAide::Command#run` method to ' \
  47.         'actually perform some work.'
  48.     end
  49.         # 返回 [CLAide::Command, nil]
  50.     def self.find_subcommand(name)
  51.       subcommands_for_command_lookup.find { |sc| sc.command == name }
  52.     end
复制代码
发现是它其实 Gem::CommandManager 类,接着查看一下 CommandManager 的 run 方法实现,在文件 command_manager.rb 中 ,有以下的实现内容:
  1. module Pod
  2.   class Command
  3.     class Install < Command
  4.       include RepoUpdate
  5.       include ProjectDirectory
  6.       
  7.       def self.options
  8.         [
  9.           ['--repo-update', 'Force running `pod repo update` before install'],
  10.           ['--deployment', 'Disallow any changes to the Podfile or the Podfile.lock during installation'],
  11.           ['--clean-install', 'Ignore the contents of the project cache and force a full pod installation. This only ' \
  12.             'applies to projects that have enabled incremental installation'],
  13.         ].concat(super).reject { |(name, _)| name == '--no-repo-update' }
  14.       end
  15.       def initialize(argv)
  16.         super
  17.         @deployment = argv.flag?('deployment', false)
  18.         @clean_install = argv.flag?('clean-install', false)
  19.       end
  20.           # 实现CLAide::Command 的抽象方法
  21.       def run
  22.         # 验证工程目录podfile 是否存在
  23.         verify_podfile_exists!
  24.         # 获取installer对象
  25.         installer = installer_for_config
  26.         # 更新pods仓库
  27.         installer.repo_update = repo_update?(:default => false)
  28.         # 设置更新标识为关闭
  29.         installer.update = false
  30.         # 透传依赖设置
  31.         installer.deployment = @deployment
  32.         # 透传设置
  33.         installer.clean_install = @clean_install
  34.         installer.install!
  35.       end
  36.     end
  37.   end
  38. end
复制代码
通过以上的源码,可以发现命令的执行,通过调用 process_args 执行,然后在 process_args 方法中进行判断命令参数,接着通过 invoke_command 来执行命令。在 invoke_command 内部,首先通过find_command 查找命令,这里find_command 主要负责查找命令相关的执行对象,需要注意的地方在以下这句:
  1. #!/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/bin/ruby
  2. #--
  3. # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
  4. # All rights reserved.
  5. # See LICENSE.txt for permissions.
  6. #++
  7. require 'rubygems'
  8. require 'rubygems/gem_runner'
  9. require 'rubygems/exceptions'
  10. required_version = Gem::Requirement.new ">= 1.8.7"
  11. unless required_version.satisfied_by? Gem.ruby_version then
  12.   abort "Expected Ruby Version #{required_version}, is #{Gem.ruby_version}"
  13. end
  14. args = ARGV.clone
  15. begin
  16.   Gem::GemRunner.new.run args
  17. rescue Gem::SystemExitException => e
  18.   exit e.exit_code
  19. end
复制代码
通过以上的操作,返回当前命令执行的实体对象,而对应的脚本匹配又是如何实现的呢(比如输入的命令是 gem install 命令)?这里的 load_and_instantiate(command_name) 的方法其实就是查找实体的具体操作,在实现中通过以下的语句来获取最终的常量的命令指令实体:
  1. def run(args)
  2.     build_args = extract_build_args args
  3.     do_configuration args
  4.     begin
  5.       Gem.load_env_plugins
  6.     rescue StandardError
  7.       nil
  8.     end
  9.     Gem.load_plugins
  10.     cmd = @command_manager_class.instance
  11.     cmd.command_names.each do |command_name|
  12.       config_args = Gem.configuration[command_name]
  13.       config_args = case config_args
  14.                     when String
  15.                       config_args.split " "
  16.                     else
  17.                       Array(config_args)
  18.       end
  19.       Gem::Command.add_specific_extra_args command_name, config_args
  20.     end
  21.     cmd.run Gem.configuration.args, build_args
  22.   end
复制代码
上面的语句是通过 Gem::Commands 查找类中的常量,这里的常量其实就是对应gem相关的一个个指令,在gem中声明了很多命令的常量,他们继承自 Gem::Command 基类,同时实现了抽象方法 execute,这一点很重要。比如在 install_command.rb 中定义了命令 gem install 的具体的实现:
  1. def initialize
  2.     @command_manager_class = Gem::CommandManager
  3.     @config_file_class = Gem::ConfigFile
  4. end
复制代码
在 invoke_command 方法中,最终通过 invoke_with_build_args 来最终执行命令,该方法定义Gem::Command中,在 command.rb 文件中,可以看到内容如下:
  1.   ##
  2.   # Run the command specified by +args+.
  3.   def run(args, build_args=nil)
  4.     process_args(args, build_args)
  5.   # 异常处理
  6.   rescue StandardError, Timeout::Error => ex
  7.     if ex.respond_to?(:detailed_message)
  8.       msg = ex.detailed_message(highlight: false).sub(/\A(.*?)(?: \(.+?\))/) { $1 }
  9.     else
  10.       msg = ex.message
  11.     end
  12.     alert_error clean_text("While executing gem ... (#{ex.class})\n    #{msg}")
  13.     ui.backtrace ex
  14.     terminate_interaction(1)
  15.   rescue Interrupt
  16.     alert_error clean_text("Interrupted")
  17.     terminate_interaction(1)
  18.   end
  19.   def process_args(args, build_args=nil)
  20.         # 空参数退出执行
  21.     if args.empty?
  22.       say Gem::Command::HELP
  23.       terminate_interaction 1
  24.     end
  25.         # 判断第一个参数
  26.     case args.first
  27.     when "-h", "--help" then
  28.       say Gem::Command::HELP
  29.       terminate_interaction 0
  30.     when "-v", "--version" then
  31.       say Gem::VERSION
  32.       terminate_interaction 0
  33.     when "-C" then
  34.       args.shift
  35.       start_point = args.shift
  36.       if Dir.exist?(start_point)
  37.         Dir.chdir(start_point) { invoke_command(args, build_args) }
  38.       else
  39.         alert_error clean_text("#{start_point} isn't a directory.")
  40.         terminate_interaction 1
  41.       end
  42.     when /^-/ then
  43.       alert_error clean_text("Invalid option: #{args.first}. See 'gem --help'.")
  44.       terminate_interaction 1
  45.     else
  46.           # 执行命令
  47.       invoke_command(args, build_args)
  48.     end
  49.   end
  50.   
  51.   def invoke_command(args, build_args)
  52.     cmd_name = args.shift.downcase
  53.     # 查找指令,并获取继承自 Gem::Commands的实体子类(实现了excute抽象方法)
  54.     cmd = find_command cmd_name
  55.     cmd.deprecation_warning if cmd.deprecated?
  56.     # 执行 invoke_with_build_args 方法(该方法来自基类 Gem::Commands)
  57.     cmd.invoke_with_build_args args, build_args
  58.   end
  59.   def find_command(cmd_name)
  60.     cmd_name = find_alias_command cmd_name
  61.     possibilities = find_command_possibilities cmd_name
  62.     if possibilities.size > 1
  63.       raise Gem::CommandLineError,
  64.             "Ambiguous command #{cmd_name} matches [#{possibilities.join(", ")}]"
  65.     elsif possibilities.empty?
  66.       raise Gem::UnknownCommandError.new(cmd_name)
  67.     end
  68.         # 这里的[] 是方法调用,定义在下面
  69.     self[possibilities.first]
  70.   end
  71.   ##
  72.   # Returns a Command instance for +command_name+
  73.   def [](command_name)
  74.     command_name = command_name.intern
  75.     return nil if @commands[command_name].nil?
  76.     # 调用 `load_and_instantiate` 方法来完成这个过程,并将返回的对象存储到 `@commands` 哈希表中,这里 ||= 是默认值内容,类似于OC中的?:
  77.     @commands[command_name] ||= load_and_instantiate(command_name)
  78.   end
  79.   # 命令分发选择以及动态实例
  80.   def load_and_instantiate(command_name)
  81.     command_name = command_name.to_s
  82.     const_name = command_name.capitalize.gsub(/_(.)/) { $1.upcase } << "Command"
  83.     load_error = nil
  84.     begin
  85.       begin
  86.         require "rubygems/commands/#{command_name}_command"
  87.       rescue LoadError => e
  88.         load_error = e
  89.       end
  90.       # 通过 Gem::Commands 获取注册的变量
  91.       Gem::Commands.const_get(const_name).new
  92.     rescue StandardError => e
  93.       e = load_error if load_error
  94.       alert_error clean_text("Loading command: #{command_name} (#{e.class})\n\t#{e}")
  95.       ui.backtrace e
  96.     end
  97.   end
复制代码
可以看出来,最终基类中的 invoke_with_build_args 中调用了抽象方法 execute 来完成命令的运行调用。在rubyGems里面声明了很多变量,这些变量在 CommandManager 中通过 run 方法进行命令常量实体的查找,最终通过调用继承自 Gem:Command 子类的 execute 完成相关指令的执行。在rubyGems中可以看到很多变量,一个变量对应一个命令,如下所示:

到这里,我们基本可以知道整个gem命令的查找到调用的整个流程。那么 gem install 的过程中又是如何自动生成并注册相关的gem命令到系统环境变量中的呢?基于上面的命令查找调用流程,其实只需要在 install_command.rb 中查看 execute 具体的实现就清楚了,如下:
  1. @commands[command_name] ||= load_and_instantiate(command_name)
复制代码
可以看出,最终通过request_set.install 来完成最终的gem安装,而request_set 是Gem::RequestSet 的实例对象,接着在 request_set.rb 中查看相关的实现:
  1. Gem::Commands.const_get(const_name).new
复制代码
从上面的源码可以知道,整个的 Podfile 的读取流程如下: 1. 判断路径是否合法,不合法抛出异常 2. 判断扩展名类型,如果是 '', '.podfile', '.rb' 扩展按照 ruby 语法规则解析,如果是yaml则按照 yaml 文件格式解析,以上两者如果都不是,则抛出格式解析异常 3. 如果解析按照 Ruby 格式解析的话过程如下:
• 按照utf-8格式读取 Podfile 文件内容,并存储到 contents 中
• 内容符号容错处理,主要涉及" “”‘’‛" 等 符号,同时输出警告信息
• 实例 Podfile 对象,同时在实例过程中初始化 TargetDefinition 对象并配置默认的Target 信息
• 最终通过 eval(contents, nil, path.to_s) 方法执行 Podfile 文件内容完成配置记录
这里或许有一个疑问:Podfile里面定义了 Cocoapods 自己的一套DSL语法,那么执行过程中是如何解析DSL语法的呢?上面的源码文件中,如果仔细查看的话,会发现有下面这一行内容:
  1.   def execute
  2.     if options.include? :gemdeps
  3.       install_from_gemdeps
  4.       return # not reached
  5.     end
  6.     @installed_specs = []
  7.     ENV.delete "GEM_PATH" if options[:install_dir].nil?
  8.     check_install_dir
  9.     check_version
  10.     load_hooks
  11.     exit_code = install_gems
  12.     show_installed
  13.     say update_suggestion if eglible_for_update?
  14.     terminate_interaction exit_code
  15.   end
复制代码
不错,这就是DSL解析的本体,其实你可以将DSL语法理解为基于Ruby定义的一系列的领域型方法,DSL的解析的过程本质是定义的方法执行的过程。在Cocoapods中定义了很多DSL语法,定义与实现均放在了 cocoapods-core 这个核心组件中,比如在dsl.rb 文件中的以下关于Podfile的 DSL定义(摘取部分):
  1.   def invoke_with_build_args(args, build_args)
  2.     handle_options args
  3.     options[:build_args] = build_args
  4.     if options[:silent]
  5.       old_ui = ui
  6.       self.ui = ui = Gem::SilentUI.new
  7.     end
  8.     if options[:help]
  9.       show_help
  10.     elsif @when_invoked
  11.       @when_invoked.call options
  12.     else
  13.       execute
  14.     end
  15.   ensure
  16.     if ui
  17.       self.ui = old_ui
  18.       ui.close
  19.     end
  20.   end
  21.   # 子类实现该抽象完成命令的具体实现
  22.   def execute
  23.     raise Gem::Exception, "generic command has no actions"
  24.   end
复制代码
看完 DSL的定义实现是不是有种熟悉的味道,对于使用Cocoapods的使用者而言,在没有接触Ruby的情况下,依旧能够通过对Podfile的简单配置来实现三方库的管理依赖,不仅使用的学习成本低,而且能够很容易的上手,之所以能够这么便捷,就体现出了DSL的魅力所在。
对于**领域型语言**的方案选用在很多不同的业务领域中都有了相关的应用,它对特定的**业务领域场景**能够提供**高效简洁**的实现方案,对使用者友好的同时,也能提供高质量的领域能力。**cocoapods**就是借助Ruby强大的面向对象的脚本能力完成**Cocoa库**管理的实现,有种偷梁换柱的感觉,为使用者提供了领域性语言,让其更简单更高效,尤其是使用者并没有感知到其本质是**Ruby**记得一开始使用Cocoapods的时候,曾经一度以为它是一种新的语言,现在看来都是Cocoapods的DSL所给我们的错觉,毕竟使用起来实在是太香了。
作者:京东零售 李臣臣
来源:京东云开发者社区 转载请注明来源

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

千千梦丶琪

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表