美文网首页
CocoaPod高级篇

CocoaPod高级篇

作者: summer201704 | 来源:发表于2017-12-18 23:39 被阅读0次

    目录

    • xcconfig文件

    • CocoaPod的实现

      • pod install的过程

    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中的依赖
    • 下载依赖
    • 创建Pods.xcodeproj工程
    • 继承workspace
    解析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
    

    这个方法做了几件小事:

    • 生成Pods.xcodeproj工程
    • 将依赖中的文件加入工程
    • 将依赖中的Library加入工程
    • 设置目标依赖(Target Dependencies)
      这几件事情都离不开CocoaPods的另外一个组件Xcodeproj,这个一个可以操作一个Xcode工程中的Group以及文件的组件,我们都知道对对Xcode工程的修改大多数情况下都是对一个名为project.pdxproj的文件进行修改,而Xcodeproj这个组件就是CocoaPods团队开发的用于操作这个文件的第三方库。
    生成workspace

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

    def integrate!
      create_workspace
      integrate_user_targets
      warn_about_xcconfig_overrides
      save_projects
    end
    

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

    相关文章

      网友评论

          本文标题:CocoaPod高级篇

          本文链接:https://www.haomeiwen.com/subject/hsoyixtx.html