CocoaPod高级篇

2017-12-18  本文已影响0人  summer201704

目录

xcconfig文件

作用:工程的配置文件。

可以通过在文件中的代码指定工程中依赖的库(OTHER_LDFLAGS),header_search_path(HEADER_SEARCH_PATHS)。在cocoapods里面还有其他的功能

参考:https://www.objc.io/issues/6-build-tools/cocoapods-under-the-hood/

CocoaPod的实现

源码位置:/Library/Ruby/Gems/#gem版本号#/gems/cocoapods-#版本号#

pod install的过程

首先在CocoaPods中,所有的命令都会由Command类派发到将对应的类,而真正执行pod install的类就是install:

  module Pod
  class Command
    class Install < Command
      def run
        verify_podfile_exists!
        installer = installer_for_config
        installer.repo_update = repo_update?(:default => false)
        installer.update = false
        installer.install!
      end
    end
    end
  end

这里面会从配置类的事例config中获取一个Installer的事例,然后执行install!方法,这里的installer有一个update属性,而这也就是pod install 和 update之间最大的区别,其中后者会无视已有的Podfile.lock文件,重新对依赖进行分析:

module Pod
  class Command
    class Install < Command
      def run
        verify_podfile_exists!
        installer = installer_for_config
        installer.repo_update = repo_update?(:default => true)
        installer.update = true
        installer.install!
      end
    end
    end
  end

Podfile的解析:

通过ruby的eval将读取到的文件字符串转换成ruby代码,然后有CocoaPods-Core这个模块来完成。而这个过程早在installer_for_config中就已经开始了:

def installer_for_config
  Installer.new(config.sandbox, config.podfile, config.lockfile)
end

这个方法会从config.podfile中取出一个podfile类的实例:

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

类方法Podfile.from_file就定义在CocoaPods-Core这个库中,用于分析Podfile中定义的依赖,这个方法会根据Podfile不同的类型选择不同的调用路径:

Podfile.from_file
`-- Podfile.from_ruby
  |-- File.open
  `-- eval

from_ruby类方法就是将podfile文件读取到数据中,然后使用eval直接将文件中的内容当做Ruby代码来执行。

def self.from_ruby(path, contents = nil)
contents ||= File.open(path, 'r:utf-8', &:read)

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这个类的顶部,我们使用Ruby的Mixin的语法来混入Podfile中代码执行所需要的上下文:

include Pod::Podfile::DSL

Podfile中的所有你见到的方法都是定义在DSL这个模块下面的:

module Pod
class Podfile
  module DSL
    def pod(name = nil, *requirements) end
    def target(name, options = nil) end
    def platform(name, target = nil) end
    def inhibit_all_warnings! end
    def use_frameworks!(flag = true) end
    def source(source) end
    ...
  end
end
end

这里定义了很多Podfile中使用的方法,当使用eval执行文件中的代码时,就会执行这个模块里的方法,在这里简单看一下其中几个方法的实现,比如说source方法:

def source(source)
  hash_sources = get_hash_value('sources') || []
  hash_sources << source
  set_hash_value('sources', hash_sources.uniq)
end

该方法会将新的source加入已有的源数组中,然后更新原有的source对应的值。

稍微复杂一些的是target方法:

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

这个方法会创建一个 TargetDefinition 类的实例,然后将当前环境系的 target_definition 设置成这个刚刚创建的实例。这样,之后使用 pod 定义的依赖都会填充到当前的 TargetDefinition 中:

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

  current_target_definition.store_pod(name, *requirements)
end

当pod方法被调用时,会执行store_pod将依赖存储到当前target中的dependencies数组中:

def store_pod(name, *requirements)
  return if parse_subspecs(name, requirements)
  parse_inhibit_warnings(name, requirements)
  parse_configuration_whitelist(name, requirements)

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

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

上面的流程就完成了对podfile的解析。

安装依赖的过程

Podfile被解析后的内容会被转化成一个Podfile类的实例,而Installer的实例方法install!就会使用这些安装当前工程的依赖,而整个安装依赖的过程大约有四个部分:

解析Podfile中的依赖
def install!
  resolve_dependencies
  download_dependencies
  generate_pods_project
  integrate_user_project
end

在上面的install方法调用的resolve_dependencies会创建一个Analyzer类的实例,在这个方法中,你会看到一些非常熟悉的字符串:

def resolve_dependencies
 analyzer = create_analyzer

 plugin_sources = run_source_provider_hooks
 analyzer.sources.insert(0, *plugin_sources)

 UI.section 'Updating local specs repositories' do
   analyzer.update_repositories
 end if repo_update?

 UI.section 'Analyzing dependencies' do
   analyze(analyzer)
   validate_build_configurations
   clean_sandbox
 end
end

在使用CocoaPods中经常出现的Updating local specs repositories以Analyzing dependencies 就是从这里输出到终端的。

下载依赖

在依赖关系解决返回了一系列Specification对象之后,就到了Pod install 的第二部分,下载依赖:

def install_pod_sources
  @installed_specs = []
  pods_to_install = sandbox_state.added | sandbox_state.changed
  title_options = { :verbose_prefix => '-> '.green }
  root_specs.sort_by(&:name).each do |spec|
    if pods_to_install.include?(spec.name)
      if sandbox_state.changed.include?(spec.name) && sandbox.manifest
        previous = sandbox.manifest.version(spec.name)
        title = "Installing #{spec.name} #{spec.version} (was #{previous})"
      else
        title = "Installing #{spec}"
      end
      UI.titled_section(title.green, title_options) do
        install_source_of_pod(spec.name)
      end
    else
      UI.titled_section("Using #{spec}", title_options) do
        create_pod_installer(spec.name)
      end
    end
  end
end

在这个方法中你会看到更多熟悉的提示,CocoaPods会使用沙盒(sandbox)存储已有依赖的数据,在更新现有的依赖时,会根据依赖的的不同状态显示不同的提示信息:

-> Using AFNetworking (3.1.0)

-> Using AKPickerView (0.2.7)

-> Using BlocksKit (2.2.5) was (2.2.4)

-> Installing MBProgressHUD (1.0.0)
...

下载的方法在CocoaPods-Download中:

def self.download_source(target, params)
  FileUtils.rm_rf(target)
  downloader = Downloader.for_target(target, params)
  downloader.download
  target.mkpath

  if downloader.options_specific?
    params
  else
    downloader.checkout_options
  end
end

方法中调用的for_target根据不同的源会创建一个下载器,因为依赖可能通过不同的协议或者方式进行下载,比如说Git/HTTP/SVN等等,组件CocoaPods-Downloader就会根据Podfile中依赖的参数选项使用不同的方法下载依赖。

大部分的依赖都会被下载到~/Library/Caches/CocoaPos/Pods/Release这个文件夹中,然后从这个这里复制到项目工程目录下的./Pods中,这也就完成了整个CocoaPods的下载流程。

生成Pods.xcodeproj

CocoaPods通过组件CocoaPods-Downloader已经成功将所有的依赖下载到了当前的工程中,这里会将所有的依赖打包到Pods.xcodeproj中:

def generate_pods_project(generator = create_generator)
  UI.section 'Generating Pods project' do
    generator.generate!
    @pods_project = generator.project
    run_podfile_post_install_hooks
    generator.write
    generator.share_development_pod_schemes
    write_lockfiles
  end
end

generate_pods_project中会执行PodsProjectGenerator的实例方法generate!:

def generate!
  prepare
  install_file_references
 install_libraries
  set_target_dependencies
end

这个方法做了几件小事:

生成workspace

最后的这一部分与生成Pods.xcodeproj的过程有一些相似,这里使用的类是UserProjectIntegrator,调用方法integrate!时,就会开始集成工程所需要的Target:

def integrate!
  create_workspace
  integrate_user_targets
  warn_about_xcconfig_overrides
  save_projects
end

整个Pod install的过程就结束了。

上一篇下一篇

猜你喜欢

热点阅读