老青菜

CocoaPods - 源码篇

2014-10-14

上一篇 我们创建了自己的私有 Pods 库,在 Podfile 文件里,我们可以对Pods project进行配置。

前言


由于项目里业务线很多,集成了很多第三方pod库 和 私有pod库,整个pod project体积非常大。默认的Xcode 编译行为寻找依赖的project进行编译,并且是并行的。


我们做了如下改动,加快主工程编译速度:

.去除了主工程对 Pods target的依赖编译
.取消上面两个勾选
.在 Manage Scheme 里勾选了Pods project,以便于可以手动选择 Pods project进行编译

如果对Pods库更改了,我们可以手动选择 Pods Scheme 进行编译,然后再编译主工程,这样避免Pods不必要编译。


问题

每次pod install之后,pods scheme 自动消失了,我们找到 xcschememanagement.plist文件

cd demo/Pods/Pods.xcodeproj/xcuserdata/Green.xcuserdatad/xcschemes
cat xcschememanagement.plist

# 输出
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
    <key>Pods.xcscheme</key>
    <dict>
        <key>isShown</key>
        <false/>
    </dict>
...

可以看到:Pods.xcscheme isShownfalse,这样导致 scheme 中没有Pods

Pod Install 剖析

我们来看看 CocoaPods 源码

cd /Library/Ruby/Gems/2.0.0/gems/
#这里有很多版本,我们只看0.38.2

当我们执行 pod install,其实调用到Installer对象,

    #文件位置:`cocoapods-0.38.2/lib/cocoapods/command/project.rb`

    #初始化 Installer 对象       
    def run_install_with_update(update)
        installer = Installer.new(config.sandbox, config.podfile, config.lockfile)
        installer.update = update

        #install 方法
        installer.install!
      end
    end
install!
    #文件位置:`cocoapods-0.38.2/lib/cocoapods/installer.rb`文件,

      #install 方法
    def install!
      prepare
      resolve_dependencies

      #下载依赖
      download_dependencies
      determine_dependency_product_types
      verify_no_duplicate_framework_names
      verify_no_static_framework_transitive_dependencies
      verify_framework_usage

      #合成 pods project
      generate_pods_project

      integrate_user_project if config.integrate_targets?
      perform_post_install_actions
    end
download_dependencies
    #下载pods 资源
    def download_dependencies
      UI.section 'Downloading dependencies' do
        create_file_accessors
        install_pod_sources
        run_podfile_pre_install_hooks
        clean_pod_sources
      end
    end
generate_pods_project
    #合成 pods project
    def generate_pods_project
      UI.section 'Generating Pods project' do
        prepare_pods_project
        install_file_references
        install_libraries
        set_target_dependencies

        #执行Podfile 的post_install 代码块
        run_podfile_post_install_hooks

        #重新写入pod project,就是在这里修改了所有`pod.xcscheme` 的`isShown`为false
        write_pod_project

        share_development_pod_schemes
        write_lockfiles
      end
    end
run post_install
    #执行Podfile 的post_install 代码块
    def run_podfile_post_install_hooks
      UI.message '- Running post install hooks' do
        executed = run_podfile_post_install_hook
        UI.message '- Podfile' if executed
      end
    end


    def run_podfile_post_install_hook
        #执行 post_install,这个代码块(block)可以在Podfile里指定
      podfile.post_install!(self)
    rescue => e
      raise Informative, 'An error occurred while processing the post-install ' \
        'hook of the Podfile.' \
        "\n\n#{e.message}\n\n#{e.backtrace * "\n"}"
    end
post_install

我们经常在 Podfile 里设置 @post_install 代码块:

    #文件位置:`cocoapods-core-0.38.2/lib/cocoapods-core/podfile/dsl.rb` 文件

      def post_install(&block)
        @post_install_callback = block
      end    
write_pod_project
    # 重新写入pod project,就是在这里修改了所有`pod.xcscheme` 的`isShown`为false
    def write_pod_project
      UI.message "- Writing Xcode project file to #{UI.path sandbox.project_path}" do
        pods_project.pods.remove_from_project if pods_project.pods.empty?
        pods_project.development_pods.remove_from_project if pods_project.development_pods.empty?
        pods_project.sort(:groups_position => :below)

        ##重新创建schemes 文件,这里更改了isShown
        pods_project.recreate_user_schemes(false)
        pods_project.predictabilize_uuids if config.deterministic_uuids?
        pods_project.save
      end
    end  
recreate_user_schemes
      #重新创建schemes 文件
    def recreate_user_schemes(visible = true)
      schemes_dir = XCScheme.user_data_dir(path)
      FileUtils.rm_rf(schemes_dir)
      FileUtils.mkdir_p(schemes_dir)
      xcschememanagement = {}
      xcschememanagement['SchemeUserState'] = {}
      xcschememanagement['SuppressBuildableAutocreation'] = {}

      targets.each do |target|
        scheme = XCScheme.new
        scheme.add_build_target(target)
        scheme.save_as(path, target.name, false)
        xcschememanagement['SchemeUserState']["#{target.name}.xcscheme"] = {}

        #就是在这里修改的。
        xcschememanagement['SchemeUserState']["#{target.name}.xcscheme"]['isShown'] = visible
      end

      xcschememanagement_path = schemes_dir + 'xcschememanagement.plist'
      Xcodeproj.write_plist(xcschememanagement, xcschememanagement_path)
    end

这样我们找到了根本问题,其实还是底层做了限制,每次 pod install 会重新生成 scheme 文件,并且每个 pod target 的 isShown 都是 false 。

Podfile 剖析

Podfile 其实是个 Ruby 类,对应 Cocoapods 的 Podfile class,我们可以看看Podfile class源码:

     # 文件位置:/Library/Ruby/Gems/2.0.0/gems/cocoapods-core-0.38.2/lib/cocoapods-core/podfile/dsl.rb

     module Pod 
      class Podfile
      module DSL
      ...
platform

指定 Pods targetplatform

  # @!group Target configuration
  #   These settings are used to control the  CocoaPods generated project.
  #
  #   This starts out simply with stating what `platform` you are working  on. `xcodeproj` allows you to state specifically which project to link with.

  # Specifies the platform for which a static library should be built.
  #
  # CocoaPods provides a default deployment target if one is not specified.
  # The current default values are `4.3` for iOS, `10.6` for OS X and `2.0` for watchOS.
  #
  # If the deployment target requires it (iOS < `4.3`), `armv6` architecture will be added to `ARCHS`.
  #
  # @param    [Symbol] name
  #           the name of platform, can be either `:osx` for OS X, `:ios`
  #           for iOS or `:watchos` for watchOS.
  #
  # @param    [String, Version] target
  #           The optional deployment.  If not provided a default value
  #           according to the platform name will be assigned.
  #
  # @example  Specifying the platform
  #
  #           platform :ios, "4.0"
  #           platform :ios
  #
  # @return   [void]

  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
xcodeproj

指定 Pods libraries 可以被哪个 project 链接。

  # @Specifies the Xcode project that contains the target that the Pods library should be linked with.
  # 
  # @param    [String] path
  #           the path of the project to link with
  #
  # @param    [Hash{String => symbol}] build_configurations
  #           a hash where the keys are the name of the build
  #           configurations in your Xcode project and the values are
  #           Symbols that specify if the configuration should be based on
  #           the `:debug` or `:release` configuration. If no explicit
  #           mapping is specified for a configuration in your project, it
  #           will default to `:release`.
  #
  # @example  Specifying the user project
  #
  #           # Look for target to link with in an Xcode project called
  #           # `MyProject.xcodeproj`.
  #           xcodeproj 'MyProject'
  #
  #           target :test do
  #             # This Pods library links with a target in another project.
  #             xcodeproj 'TestProject'
  #           end
  #
  # @example  Using custom build configurations
  #
  #           xcodeproj 'TestProject', 'Mac App Store' => :release, 'Test' => :debug
  #
  #
  # @return   [void]


  def xcodeproj(path, build_configurations = {})
    current_target_definition.user_project_path = path
    current_target_definition.build_configurations = build_configurations
  end
inhibit_all_warnings

指定 是否需要忽略警告

  # @Inhibits **all** the warnings from the CocoaPods libraries.
  #
  #
  # This attribute is inherited by child target definitions.
  #
  # If you would like to inhibit warnings per Pod you can use the following syntax:
  #
  #     pod 'SSZipArchive', :inhibit_warnings => true

  def inhibit_all_warnings!
    current_target_definition.inhibit_all_warnings = true
  end
use_frameworks

指定是否使用 framework

  # @Use frameworks instead of static libraries for Pods.
  #
  # ------
  #
  # This attribute is inherited by child target definitions.
  #    

  def use_frameworks!(flag = true)
    current_target_definition.use_frameworks!(flag)
  end
workspace

指定 合成的 workspace 路径

  # @!group Workspace
  #
  #   This group list the options to configure workspace and to set global settings.
  # Specifies the Xcode workspace that should contain all the projects.
  #
  # -----
  #
  # If no explicit Xcode workspace is specified and only **one** project
  # exists in the same directory as the Podfile, then the name of that project is used as the workspace’s name.
  #
  # @param    [String] path
  #           path of the workspace.
  #
  # @example  Specifying a workspace
  #
  #           workspace 'MyWorkspace'
  #
  # @return   [void]


  def workspace(path)
    set_hash_value('workspace', path.to_s)
  end
source

指定 specs 仓库源

      # @!group Sources
      #
      #   The Podfile retrieves specs from a given list of sources (repositories).
      #
      #   Sources are __global__ and they are not stored per target definition.
      # Specifies the location of specs
      #
      # -----
      #
      # Use this method to specify sources. The order of the sources is
      # relevant. CocoaPods will use the highest version of a Pod of the first
      # source which includes the Pod (regardless whether other sources have a
      # higher version).
      #
      # @param    [String] source
      #           The URL of a specs repository.
      #
      # @example  Specifying to first use the Artsy repository and then the
      #           CocoaPods Master Repository
      #
      #           source 'https://github.com/artsy/Specs.git'
      #           source 'https://github.com/CocoaPods/Specs.git'
      #
      # @return   [void]

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

设置 installer 之后的执行的代码块

      # This hook allows you to make any last changes to the generated Xcode
      # project before it is written to disk, or any other tasks you might want to perform.
      #
      # It receives the [`Pod::Installer`](http://rubydoc.info/gems/cocoapods/Pod/Installer/) as its only argument.
      #
      # @example  Customising the build settings of all targets
      #
      #   post_install do |installer|
      #     installer.pods_project.targets.each do |target|
      #       target.build_configurations.each do |config|
      #         config.build_settings['GCC_ENABLE_OBJC_GC'] = 'supported'
      #       end
      #     end
      #   end
      #
      # @return   [void]
      #

      def post_install(&block)
        @post_install_callback = block
      end

Podfile 自定义

我们在Podfile中增加如下代码:

    #设置 Podfile 对象 @post_install_callback 成员
    self.post_install do |installer|
    $KDPod_Project
    begin
        $KDPod_Project=installer.project
        rescue
        puts "installer.project is delete"
        $KDPod_Project=installer.pods_project
    end
    installer.use_default_plugins = false
    $KDPod_Project.targets.each do |target|
        #设置ORGANIZATIONNAME 、 CLASSPREFIX
        #target.project.root_object.attributes['ORGANIZATIONNAME']='xxxxx.xxx'
        #target.project.root_object.attributes['CLASSPREFIX']='xxxx'
        target.build_configurations.each do |config|

            #设置target的编译后生成目录
            config.build_settings['CONFIGURATION_TEMP_DIR'] = './build'
            config.build_settings['ONLY_ACTIVE_ARCH'] = 'NO'
        end
    end

    # 延迟执行
    def kdperformSelector(time)
        before=Time.now
        fork do
            sleep(1) until Time.now-before >= time
            yield
        end
    end


    #延迟3秒 更改pods scheme visabled
    kdperformSelector(3){
        ##update pod visabled
        $schemes_dir = Xcodeproj::XCScheme.user_data_dir($KDPod_Project.path)
        $xcschememanagement_path = $schemes_dir + 'xcschememanagement.plist'
        $xcschememanagement_content = Xcodeproj.read_plist($xcschememanagement_path)

        #设置 isShown 属性
        $xcschememanagement_content['SchemeUserState']["Pods.xcscheme"]['isShown'] = true
        FileUtils.rm_rf($xcschememanagement_path)
        Xcodeproj.write_plist($xcschememanagement_content,$xcschememanagement_path)
        #    puts $xcschememanagement_content
        #    $KDPod_Project.recreate_user_schemes(true)
        $KDPod_Project.save
        puts "KDPod_Project.save"
    }
    end

我们新for一个进程,在 download_dependencies 之后 延迟了3秒执行更新 pods.xscheme isShown 属性。
这样就可以显示Pods Scheme了。

这里只是做个例子,重要的是,了解了源码,我们就可以对project进行其他配置。

使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

扫描二维码,分享此文章