我所理解的 CocoaPods

boolchow發表於2018-06-10

作者:bool周  原文連結:我所理解的 CocoaPods

很久之前讀了一遍 Cocoa Pods 官方文件,對 Cocoa Pods 有了一個簡單的瞭解。時隔多日,全忘了。

所以再回顧一下,順便寫一篇總結。文章分為原理使用兩部分,比較長,可以根據自己的需求選擇性閱讀。

概述

CocoaPods 是開發 OS X 和 iOS 應用程式的一個第三方庫的依賴管理工具,使用這個工具可以簡化對元件依、更新的過程。新新增一些第三方元件可以直接修改 podfile 然後進行 pod install;更新已有第三方元件,可以修改 podfile 然後進行 pod update;自己開發的元件也可以上傳到 CocoaPods 或者私有倉庫,供其他人使用。

CocoaPods 是用 ruby 寫的,由若干個 gems 組成。也就是說,iOS project 使用 CocoaPods 來進行元件管理,CocoaPods 本身也是一個 project,它使用 gem 進行元件管理。

開始寫這篇文章的時候,我想先寫使用,再寫原理。因為我擔心很多人感覺原理晦澀難懂,就放棄看後面了。但構思的時候發現,明白了原理之後,對一些命令的使用會有更深刻的瞭解。所以還是決定將原理放在前面講。

基本原理

1.CocoaPods 結構

CocoaPods 是用 Ruby 寫的,並由若干個 Ruby 包 (gems) 構成的,原始碼託管在 GitHub 上。其中主要的幾個元件為:

  • CocoaPods/Specs
    這個是一個儲存第三方元件 podspec 檔案的倉庫。第三方元件開發完成之後,會傳一份 podspec 檔案傳到 CocoaPods,這個 Specs 包含了每個第三方元件所有版本的 podspec 檔案。當使用某個第三方元件時,如果這些元件支援 CocoaPods,會在 Podfile 中指定 source,例如下面這樣:

    source 'https://github.com/CocoaPods/Specs.git'
    複製程式碼

    當執行 pod installpod update 等一些命令時,便會從這個倉庫找到元件指定版本的 podspec 檔案,然後根據這個 podspec 配置資訊去獲取元件。

  • CocoaPods/CocoaPods
    這是是一個面向使用者的元件,每當執行一個 pod 命令時,這個元件都將被啟用。該元件包括了所有使用 CocoaPods 涉及到的功能,並且還能通過呼叫所有其它的 gems 來執行任務。

  • CocoaPods/Core
    這個 gem 元件提供支援與 CocoaPods 相關檔案的處理,例如 SpecificationPodfileSource。當執行 pod install 等一些命令時。Core 元件會解析第三方元件開發者上傳的 podspec 檔案和使用者的 podfile,以此確定需要為 project 引入哪些檔案。除此之外,當執行與這些檔案一些相關的命令時,也由這部分元件處理,例如使用 pod spec lint 來檢測 podspec 檔案的有效性。

  • CocoaPods/Xcodeproj
    使用這個 gem 元件,你可以用 ruby 來建立並修改 Xcode projects。在 CocoaPods 中它負責所有工程檔案的整合。如果你想要寫一個指令碼來方便的修改工程檔案,那麼可以單獨下載這個 gem 並使用。更多資訊可以檢視工程的 readme

2.幾個相關檔案

  • Specification

    這個檔案用來描述第三方元件某個版本的資訊。主要包含了元件拉取地址、應該拉取那些檔案和專案配置資訊。除此之外,還包含一些元件資訊,例如元件的名字、版本等。後面章節會詳細講解欄位含義和檔案書寫規範。

  • Podfile

    這個檔案用來指定工程中依賴了那些元件。主要包含了依賴的元件名、元件版本、元件地址等。後面章節會詳細講解欄位含義和檔案書寫規範。

  • Podfile.lock

    在第一次執行 pod install 時,執行完畢後會生成一個 podfile.lock 檔案。這個檔案主要標註了專案當前依賴的具體版本。看下面這個檔案資訊:

    PodfileLock.png

有個問題需要牢記:CocoaPods 強烈建議將 Podfile.lock 檔案加入版本管理,這樣其他人同步了你的 podfile.lock 檔案之後,執行 pod install 時會將按照裡面指定給的版本載入,避免多人協作時發生衝突。後面的 pod install vs pod update 會詳細講解 podfile.lock 變更時機。

  • Manifest.lock

Manifest.lock 是由 Podfile.lock 生成的一個副本,每次生成或者更新 Podfile.lock,都會更下 Pods 資料夾下面的 Manifest.lock 檔案。如果你遇見過這樣的錯誤 沙盒檔案與 Podfile.lock 檔案不同步 (The sandbox is not in sync with the Podfile.lock),這是因為 Manifest.lock 檔案和 Podfile.lock 檔案不一致所引起。

3.相互關係

三者關係

上圖為元件開發者、CocoaPods、元件使用者三者的關係。

元件開發者開發完元件之後,會將元件上傳到倉庫 (Github or other)。然後建立一個 podspec 檔案,檔案中包含了使用元件時需要載入哪些檔案以及從哪裡載入。然後會將這個檔案上傳到 CocoaPods(也可以上傳至私人構建的 spec 管理倉庫)。

元件使用者想要使用某個元件,會在 Podfile 中指定元件的名字、版本、載入源以及更加詳細的資訊(例如想要載入某個 commit)。然後執行相關 Pod 命令。

CocoaPods 執行 Pod 命令,然後解析對應的 podspec 檔案,確定需要載入的檔案資訊並將檔案載入到專案工程裡。並建立 Podfile.lock、Manifest.lock、Pods.xcodeproj 等檔案。

4.一次詳細的載入過程

前面提到 CocoaPods 是開源的,所以我們可以把原始碼下載下來進行研究。pod install 這個命令定義在 CocoaPods/Core 這 gem 中。

執行 pod install 命令

所有命令都是通過 Command 類管理的,執行 pod install 時程式碼如下:

# CocoaPods/lib/cocoapods/command/install.rb
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
複製程式碼

執行時會先生成一個 installer 例項。然後設定 repo_update 屬性和 update 屬性,最後執行 install 方法。

Podfile 解析

執行 pod install 命令具體細節前,首先要解析 Podfile。這一過程在初始化 installer 例項時就已經開始:

def installer_for_config
  Installer.new(config.sandbox, config.podfile, config.lockfile)
end
複製程式碼

pod install 方法定義

pod install 方法定義如下:

# CocoaPods/lib/cocoapods/installer.rb
def install!
  prepare
  resolve_dependencies
  download_dependencies
  validate_targets
  generate_pods_project
  if installation_options.integrate_targets?
    integrate_user_project
  else
    UI.section 'Skipping User Project Integration'
  end
  perform_post_install_actions
end
複製程式碼

從方法定義中,可以看出 pod install 的執行分為如下幾部:準備階段、解決依賴衝突、下載依賴檔案、校驗 target、整合 project 檔案、輸出執行結果。下面將按照這個步驟逐步分析。

準備階段

準備階段程式碼如下:

# CocoaPods/lib/cocoapods/installer.rb
def prepare
  # Raise if pwd is inside Pods
  if Dir.pwd.start_with?(sandbox.root.to_path)
    message = 'Command should be run from a directory outside Pods directory.'
    message << "\n\n\tCurrent directory is #{UI.path(Pathname.pwd)}\n"
    raise Informative, message
  end
  UI.message 'Preparing' do
    deintegrate_if_different_major_version
    sandbox.prepare
    ensure_plugins_are_installed!
    run_plugins_pre_install_hooks
  end
end
複製程式碼

首先會檢測目錄結構,是否為可執行 pod 命令的目錄,如果不是直接輸出資訊。如果可執行,則做一些準備工作。如果你的 Podfile 中寫了一些 hooks,也會在這裡執行。

解決依賴衝突

這一階段的方法定義如下:

# CocoaPods/lib/cocoapods/installer.rb
def resolve_dependencies
 plugin_sources = run_source_provider_hooks
 analyzer = create_analyzer(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
 analyzer
end
複製程式碼

根據方法定義,我們可以看出這一階段處理的事情:啟動 hooks 並建立一個 analyzer,然後使用這個 analyzer 更新本地 specs 庫、處理版本依賴。

首先是建立 analyzer,建立過程中將 Podfilelockfile 等一些檔案資訊全部傳入,並在這個類中將這些檔案解析。建立 analyzer 程式碼如下:

# CocoaPods/lib/cocoapods/installer.rb
def create_analyzer(plugin_sources = nil)
  Analyzer.new(sandbox, podfile, lockfile, plugin_sources).tap do |analyzer|
    analyzer.installation_options = installation_options
    analyzer.has_dependencies = has_dependencies?
  end
end
複製程式碼

然後是更新本地 specs 庫。從程式碼中可以看出有一個 repo_update? 判斷,也就是說這個標誌位真的時候,才會更新本地 specs 庫。也就是我們常用的一條命令:

pod repo udpate
複製程式碼

最後是處理依賴關係。其中 Podfilelockfile 也是使用 Analyzer 這個類中解析。下面是解析方法的定義 :

# CocoaPods/lib/cocoapods/installer/analyzer.rb
def analyze(allow_fetches = true)
    validate_podfile! # step1: 解析並校驗 podfile
    validate_lockfile_version! # step2: 解析並校驗 lockfile 中的庫的版本
    @result = AnalysisResult.new # step3: 新建 result 例項
    ...
    @result.specifications  = generate_specifications(resolver_specs_by_target)
    @result.targets         = generate_targets(resolver_specs_by_target)
    @result.sandbox_state   = generate_sandbox_state
    @result.specs_by_target = resolver_specs_by_target.each_with_object({}) do |rspecs_by_target, hash|
      hash[rspecs_by_target[0]] = rspecs_by_target[1].map(&:spec)
    end
    @result.specs_by_source = Hash[resolver_specs_by_target.values.flatten(1).group_by(&:source).map { |source, specs| [source, specs.map(&:spec).uniq] }]
    sources.each { |s| @result.specs_by_source[s] ||= [] }
    @result
  end
複製程式碼

最終會將解析結果儲存在一個 @result 例項中,進行後面步驟時,會使用這個解析結果。AnalysisResult 類定義如下,註釋我就不翻譯了,看原味的英文更有助於理解具體意思:

# CocoaPods/lib/cocoapods/installer/analyzer/analysis_result.rb
module Pod
  class Installer
    class Analyzer
      class AnalysisResult
        # @return [SpecsState] the states of the Podfile specs.
        attr_accessor :podfile_state

        # @return [Hash{TargetDefinition => Array<Specification>}] the
        #         specifications grouped by target.
        attr_accessor :specs_by_target

        # @return [Hash{Source => Array<Specification>}] the
        #         specifications grouped by spec repo source.
        attr_accessor :specs_by_source

        # @return [Array<Specification>] the specifications of the resolved
        #         version of Pods that should be installed.
        attr_accessor :specifications

        # @return [SpecsState] the states of the {Sandbox} respect the resolved
        #         specifications.
        attr_accessor :sandbox_state

        # @return [Array<AggregateTarget>] The aggregate targets created for each
        #         {TargetDefinition} from the {Podfile}.
        attr_accessor :targets

        # @return [Hash{TargetDefinition => Array<TargetInspectionResult>}] the
        #         results of inspecting the user targets
        attr_accessor :target_inspections

        # @return [Hash{String=>Symbol}] A hash representing all the user build
        #         configurations across all integration targets. Each key
        #         corresponds to the name of a configuration and its value to
        #         its type (`:debug` or `:release`).
        def all_user_build_configurations
          targets.reduce({}) do |result, target|
            result.merge(target.user_build_configurations)
          end
        end
      end
    end
  end
end
複製程式碼

關於 Podfile 的解析過程,有興趣的可以檢視一下 PodfileValidator 類,在目錄 CocoaPods/lib/cocoapods/installer/analyzer/podfile_validator.rb

下載依賴檔案

下載依賴檔案方法定義如下:

# CocoaPods/lib/cocoapods/installer.rb
def download_dependencies
  UI.section 'Downloading dependencies' do
    create_file_accessors
    install_pod_sources
    run_podfile_pre_install_hooks
    clean_pod_sources
  end
end
複製程式碼

這個方法中呼叫了幾個其他方法。作用分別為:建立檔案儲存器,以便向沙盒裡面寫入資料;下載資料;啟動 hooks;進行清理操作。具體每個方法的定義,可以檢視檔案原始碼。這裡主要說一下 install_pod_sources 方法。

install_pod_sources 方法定義如下:

# CocoaPods/lib/cocoapods/installer.rb
# Downloads, installs the documentation and cleans the sources of the Pods
# which need to be installed.
#
# @return [void]
#
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
複製程式碼

首先確定需要 install 的元件。這裡主要針對新加的元件和變更的元件進行 install,至於這些資訊是通過 sandbox_state 獲取的。然而 sandbox_state 方法定義如下:

# CocoaPods/lib/cocoapods/installer.rb
def sandbox_state
  analysis_result.sandbox_state
end
複製程式碼

這裡的 analysis_result 就是我們上一步中解析出的結果,在這裡用到了。

第二步建立 title 配置資訊,後面針對變更的元件,會用這個配置標記。相信每一位開發者進行 pod install 操作的時候,都會注意到變更的元件自動標記為綠色。

最後一步是下載對應檔案。這裡分了三種情況:如果元件已經下載且版本號沒有發生變化,則直接提示 “Using xxx”,如下圖中的 “YYCache” 元件;如果元件已經下載,但是版本號發生了變化,則更新元件並提示 “Installing xxx 版本號 (之前版本號)”,如下圖中的 “AFNetworking” 元件;如果元件第一次下載,則進行下載,並提示 “Installing xxx”,如下圖中的 “YYImage”。

podInstall.png

校驗 target

校驗 target 程式碼如下:

# CocoaPods/lib/cocoapods/installer.rb
def validate_targets
  validator = Xcode::TargetValidator.new(aggregate_targets, pod_targets)
  validator.validate!
end

# CocoaPods/lib/cocoapods/installer/xcode/target_validator.rb
def validate!
  verify_no_duplicate_framework_and_library_names
  verify_no_static_framework_transitive_dependencies
  verify_no_pods_used_with_multiple_swift_versions
  verify_framework_usage
end
複製程式碼

這個方法中,建立了一個 TargetValidator 例項,並呼叫 validate() 方法進行校驗。這方方法主要分為以下幾步:

  • 檢測是否有多重引用 framework 或者 library 的情況。因為一個元件可能分成多個 subspec,如果不清楚 subspec 中的依賴關係。使用時可能會出現多重引用的情況。舉個例子,下面是 “網易雲信“ 的 podspec 檔案,以及其中依賴的兩個元件的 podspec 檔案:

    # NIMKit.podspec
    Pod::Spec.new do |s| 
    
      ...
      
      s.subspec 'Full' do |cs|  
        cs.source_files = 'NIMKit/NIMKit/**/*.{h,m}' 
        cs.dependency 'NIMKit/Core' 
        cs.dependency 'NIMSDK', '~> 4.9.0' 
      end 
    
      s.subspec 'Lite' do |cs|  
        cs.source_files = 'NIMKit/NIMKit/**/*.{h,m}'  
        cs.dependency 'NIMKit/Core'  
        cs.dependency 'NIMSDK_LITE', '~> 4.9.0'  
      end  
    
      s.subspec 'Core' do |os|     
      ...
      end   
    
      s.default_subspec = 'Lite'  
    end 
    
      # NIMSDK.podspec
      Pod::Spec.new do |s|   
      ...
      s.vendored_libraries  = '**/Libs/*.a' 
      s.vendored_frameworks = '**/NIMSDK.framework','**/NIMAVChat.framework'
      ... 
     end 
      
        # NIMSDK_LITE.podspec
    Pod::Spec.new do |s|   
     ...
      s.vendored_libraries  = 'NIMSDK/Libs/*.a'
      s.vendored_frameworks = '**/NIMSDK.framework'  
     ...
    end 
    複製程式碼

然後我這樣去引用:

  pod 'NIMKit', :subspecs => ['Lite','Full']複製程式碼

因為兩個 spec 中都引用了 NIMKit framework,所以執行 `pod install` 的時候就會出現如下問題:

target_confilct.png

> 這裡還是不太理解,可能表述有誤。如果清楚請指出,我加以改正。
複製程式碼
  • 處理靜態庫傳遞依賴問題。如果 A 元件依賴 B 元件,B 元件中含有通過vendored_libraries載入的靜態庫.a或framewrok。如果 Podfile 中不使用 use_frameworks!,不會出現任何問題;如果使用 use_frameworks!,那麼打包的 framework 會將 vendored_libraries 庫中的內容包含進來,這就出現了符號衝突的問題了。如果出現了這種問題,CocoaPods 會報出如下錯誤:

    The 'pod-name' target has transitive dependencies that include static binaries: (static_libs.to_sentence)
    複製程式碼

    因為在 swift 中必須使用 `use_frameworks`,所以 swift 中經常會遇到這種問題。解決辦法就是修改 `podspec` 和 `Podfile` 兩個檔案:

    podspec:

      s.dependency 'xxx', '~> 15.2.0'
    
      s.pod_target_xcconfig = {
        'FRAMEWORK_SEARCH_PATHS' => '$(inherited) $(PODS_ROOT)/xxx',
        'OTHER_LDFLAGS'          => '$(inherited) -undefined dynamic_lookup'
      }
    複製程式碼

    podfile:

      pre_install do |installer|
        # workaround for https://github.com/CocoaPods/CocoaPods/issues/3289
        def installer.verify_no_static_framework_transitive_dependencies; end
      end複製程式碼

  • 校驗不同 target 所引用的程式碼中,如果包含 swift,所使用的 swift 版本是否相同。如果不同則會報出如下錯誤:

    The following pods are integrated into targets that do not have the same Swift version:{error_messages.join}
    複製程式碼

  • 當在 swift 中使用時,校驗是否在 Podfile 中是否新增了 use_frameworks!。如果不新增便會報錯。例如:

    Podfile

    source 'https://github.com/CocoaPods/Specs.git'
      platform :ios, '8.0'
      # ignore all warnings from all pods
      inhibit_all_warnings!
    
      target 'SwiftTest' do
         pod 'AFNetworking','3.0'
         pod 'Alamofire', '~> 4.6'
           pod 'YYCache'
         pod 'YYImage'
         pod 'YYImage'
      end
    複製程式碼

      對上述 `Podfile` 檔案,執行 `pod install` 時便會報出如下錯誤:

    [!] Pods written in Swift can only be integrated as frameworks; add `use_frameworks!` to your Podfile or target to opt into using it. The Swift Pod being used is: Alamofire
    複製程式碼

ps: 在 CocoaPods 1.5 中,已經支援 swift 使用靜態庫,具體可檢視 CocoaPods 1.5.0 — Swift Static Libraries . 寫這篇文章的時候還未支援。

整合 project 檔案

依賴檔案下載完畢之後,會將這些檔案打包成 Pods.xcodeproj。這一過程方法定義如下:

 # CocoaPods/lib/cocoapods/installer.rb
 # Generate the 'Pods/Pods.xcodeproj' project.
#
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
複製程式碼

這裡會通過 generator 例項執行 generate! 方法。我們主要說一下這個方法:

# CocoaPods/lib/cocoapods/installer/xcode/pods_project_generator.rb
def generate!
  prepare
  install_file_references
  install_libraries
  integrate_targets
  set_target_dependencies
end
複製程式碼

這個方法做了這樣幾件事:

  • 生成一個 Pods.xcodeproj 工程
  • 將下載的依賴檔案加入工程
  • 將下載的 Library 加入工程
  • 處理 target 依賴

這一系列過程的操作,主要依賴於前面所提到的 CocoaPods/Xcodeproj 元件。

執行下載過程

這是最後一個階段,會下載每個元件的具體原始檔,並輸出最終的執行結果。方法定義如下:

 # CocoaPods/lib/cocoapods/installer.rb
  # Performs any post-installation actions
#
def perform_post_install_actions
  unlock_pod_sources
  run_plugins_post_install_hooks
  warn_for_deprecations
  warn_for_installed_script_phases
  lock_pod_sources
  print_post_install_message
end
複製程式碼

這一過程一般是最慢的一個過程。偷懶一下,其中的過程方法我就不一一講解了。看一下最後輸出資訊這個方法吧:

def print_post_install_message
  podfile_dependencies = podfile.dependencies.uniq.size
  pods_installed = root_specs.size
  title_options = { :verbose_prefix => '-> '.green }
  UI.titled_section('Pod installation complete! ' \
                    "There #{podfile_dependencies == 1 ? 'is' : 'are'} #{podfile_dependencies} " \
                    "#{'dependency'.pluralize(podfile_dependencies)} from the Podfile " \
                    "and #{pods_installed} total #{'pod'.pluralize(pods_installed)} installed.".green,
                    title_options)
end
複製程式碼

也就是我們常見的輸出結果:

我所理解的 CocoaPods

執行一次 pod install 的過程到此結束了。如果你大致讀一遍原始碼,執行 pod install 再遇到問題時,可以快速斷定問題原因並修復。pod updatepod install 還是有一些差別的,有興趣的同學可以讀一下 pod update 的原始碼。我這裡就不在寫了,就算你讀不吐我都快寫吐了。

CocoaPods 使用

1.安裝 CocoaPods

這裡假設你什麼都沒有安裝,從 0 開始。如果你已經安裝了某些東西,可以跳過。

  • 安裝 rvm

    curl -L get.rvm.io | bash -s stable 
      
    source ~/.bashrc
      
    source ~/.bash_profile
    複製程式碼

  • 檢視 rvm 版本

    rvm -v
    複製程式碼

    rvm 1.29.3 (latest) by Michal Papis, Piotr Kuczynski, Wayne E. Seguin [https://rvm.io]
    複製程式碼

  • 檢視可安裝 Ruby 版本

    rvm list known
    複製程式碼

  • 安裝一個版本,我一般選最高,這裡是 2.4.1

    rvm install 2.4.1複製程式碼

  • 因為你後面可能會稀裡糊塗裝很多版本,所以設定這個版本為預設版本

    rvm use 2.4.1 --default複製程式碼

  • 安裝 CocoaPods

  // 安裝 CocoaPods
  sudo gem install cocoapods
  
  // 安裝本地庫,需要等待很長時間
  pod setup
複製程式碼

  • 如果安裝了多個 Xcode,需要選擇一個。

sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer複製程式碼
  • 檢測是否安裝好,search 一個元件,能 search 到證明安裝好了

pod search [一個元件]複製程式碼
  • CocoaPods 版本操作

// 檢視當前安裝的所有 CocoaPods 版本
gem list –local | grep cocoapods
// 當前使用 pod 版本
pod –version
// 更新到最新穩定版本
sudo gem install cocoapods
// 更新到一個 pre-release 版本
sudo gem install cocoapods –pre
// 安裝指定版本
sudo gem install cocoapods -v [版本號]
// 移除 CocoaPods,如果你安裝多個,會列出一個 list 讓你選擇刪除那個。如果只安裝一個,也會給你提示,問你是否確定刪除。
sudo gem uninstall cocopods
// 移除指定版本
sudo gem uninstall cocopods -v [版本號]
// 使用指定版本執行命令
pod 1.3.1 install
複製程式碼

2.使用 CocoaPods

  • 基礎操作

// 開啟一新的工程,執行命令
pod init
// Podfile 中新增
pod ‘AFNetworking’
// install
pod install複製程式碼
  • 想要看到 install 詳細過程

pod install –verbose複製程式碼
  • 更新某一個元件

// 不新增元件名則更新所有
pod update [元件名]複製程式碼
  • 更新本地依賴

pod repo update複製程式碼
  •  不想在 install/update 時更新本地依賴。這樣執行 `pod install` 會快一些。但是如果 github 或者私有倉庫上面有了最新版本,本地搜到的還是舊版本。如果 `Podfile` 中使用新的版本號,這樣是無法執行成功的。 

// –verbose 可省略
pod install –verbose –no-repro-update
pod update –verbose –no-repro-update
複製程式碼
  • 校驗本地 lib repo 的有效性

pod lib lint –allow-warnings複製程式碼
  • 校驗 spec 檔案

pod spec lint複製程式碼
  • 自定義元件時,將元件的 spec 檔案上傳到遠端倉庫。

// [reponame] 一般可以在路徑 ~/.cocoapods/repo 下檢視,選擇你需要的 name.
pod repo push [reponame] [name.podspec] –verbose –sources=master,[reponame] –use-libraries –allow-warnings複製程式碼
想了解更多命令,請檢視官方文件中 Command Line API 這一章節。

Podfile 書寫規範

Podfile Syntax Reference v1.4.0

source 'https://github.com/CocoaPods/Specs.git' # 元件依賴檔案所存放倉庫,根據需求可引入多個
source 'https://github.com/artsy/Specs.git'


platform :ios, '8.0'            # 
inhibit_all_warnings!           # 忽視引用的程式碼中的警告

workspace 'CocoaPodsDemo'       # 指定生成的 workspace 名字

def common_pods                 # 如果有多個 target,可以將公共部分進行 def 定義再引入
    pod 'xxx'
end

target 'CocoaPodsDemo' do
    project 'DemoProject'       # 可用於指定實際的工程

    use_frameworks!             # 是否以 framework 形式引入。swift 必須有這個關鍵字 

    common_pods              # 公共引入的元件

    pod 'SSipArchive', :inhibit_warnings => true   # 遮蔽某個 pod 的 warning

    pod 'AFNetworking', '3.2'   # 使用 3.2 版本
    pod 'YYCache', '~> 0.3'     # pod update 時最高升級到 < 1.0,不包括 1.0
    
   # Build 環境配置
   pod 'PonyDebugger', :configurations => ['Debug', 'Beta']
   pod 'PonyDebugger', :configuration => 'Debug'

   # 使用具體的某個 subspec
   pod 'QueryKit/Attribute'
   pod 'QueryKit', :subspecs => ['Attribute', 'QuerySet']
   
   # 引用本地元件
   pod 'AFNetworking', :path => '~/Documents/AFNetworking'
   
   # 使用具體倉庫
   pod 'AFNetworking', :git => 'https://github.com/gowalla/AFNetworking.git'
   # 使用具體倉庫具體分支
   pod 'AFNetworking', :git => 'https://github.com/gowalla/AFNetworking.git', :branch => 'dev'
   # 使用具體倉庫的某個 tag
   pod 'AFNetworking', :git => 'https://github.com/gowalla/AFNetworking.git', :tag => '0.7.0'
   # 使用具體倉庫的某個 commit
   pod 'AFNetworking', :git => 'https://github.com/gowalla/AFNetworking.git', :commit => '082f8319af'
    
    # 使用指定路徑的 spec 檔案
   pod 'JSONKit', :podspec => 'https://example.com/JSONKit.podspec'
   
   
   target 'ShowsApp' do
     pod 'ShowsKit'

      # Has its own copy of ShowsKit + ShowTVAuth
       target 'ShowsTV' do
       pod 'ShowTVAuth'
       end

      # Has its own copy of Specta + Expecta
      # and has access to ShowsKit via the app
     # that the test target is bundled into
      target 'ShowsTests' do
       # inherit! 有三種型別:':complete' 繼承父級所有行為;':none' 什麼行為都不繼承;':search_paths' 繼承父級的 search paths
        inherit! :search_paths
        pod 'Specta'
        pod 'Expecta'
      end
   end
end

# hook 配置, 在 preparing 階段後,install 之前
pre_install do |installer|
    
end

# hook 配置,在 pod install 之後,可用於修改工程配置等
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
複製程式碼

Podspec 書寫規範

Podspec Syntax Reference v1.4.0

Pod::Spec.new do |spec|

# 元件基本資訊配置
#
   # 元件名
  spec.name         = 'Reachability'
  # 元件版本號,命名規則遵循 [semantic versioning](https://semver.org/)
  spec.version      = '3.1.0'    
  # 許可證
  spec.license      = { :type => 'BSD' }
  # 倉庫主頁
  spec.homepage     = 'https://github.com/tonymillion/Reachability'
  # 一個作者用 spec.author = 'Darth Vader'
  spec.authors      = { 'Tony Million' => 'tonymillion@gmail.com' }
  # 元件概述
  spec.summary      = 'ARC and GCD Compatible Reachability Class for iOS and OS X.'
  # 元件原始碼地址
  spec.source       = { :git => 'https://github.com/tonymillion/Reachability.git', :tag => 'v3.1.0' }

# 元件平臺支援
#
  # 支援單平臺使用
  spec.platform = :osx, '10.8'
  spec.platform = :ios

  # 支援多平臺使用
  spec.ios.deployment_target = '6.0'
  spec.osx.deployment_target = '10.8'

# Build settings
#
  spec.dependency 'AFNetworking', '~> 1.0'    # 元件依賴的第三方庫
  spec.requires_arc = false                   # 是否要求 ARC 環境
  spec.requires_arc = ['Classes/*ARC.m', 'Classes/ARC.mm']
  spec.frameworks = 'QuartzCore', 'CoreData'  # 元件引用的 framework spec.weak_frameworks = 'Twitter', 'SafariServices' # 元件弱引用的 framework
  spec.libraries = 'xml2', 'z'                # 元件引用的 library
  ... 更多請看官方文件
  
# File patterns
#
  spec.source_files = 'Classes/**/*.{h,m}'          # 接入方使用元件時,引入的原始檔,正則匹配
  spec.public_header_files = 'Headers/Public/*.h'   # 引入的共有標頭檔案
  spec.private_header_files = 'Headers/Private/*.h' # 引入的私有標頭檔案
  spec.vendored_frameworks = 'MyFramework.framework'# 引入的 framework
  spec.vendored_libraries = 'libProj4.a'            # 引入的 library
  # 以 bundle 形式引入的資源
  spec.resource_bundles = {
    'MapBox' => ['MapView/Map/Resources/*.png'],
    'OtherResources' => ['MapView/Map/OtherResources/*.png']
  }
  # 直接引入資源
  spec.resources = ['Images/*.png', 'Sounds/*']
  ... 更多請看官方文件

# Subspecs
#
  # 將元件分為多個子元件,接入方可以根據需求只接入幾個子元件,減少包體積
  subspec 'Twitter' do |sp|
      sp.source_files = 'Classes/Twitter'
  end
  # 測試元件
  spec.test_spec do |test_spec|
    test_spec.source_files = 'NSAttributedString+CCLFormatTests.m'
    test_spec.dependency 'Expecta'
  end
  # 預設子元件。也就是當接入方不作區分時,直接使用元件名引入時,所引入子元件
  spec.default_subspec = 'Core'

# 多平臺支援
#
  spec.ios.source_files = 'Classes/ios/**/*.{h,m}'
  spec.osx.source_files = 'Classes/osx/**/*.{h,m}'
複製程式碼

Cocopods 基本使用內容就這些。具體可以檢視官方文件中 Reference 這一章節。

一些問題

這裡是一些經常遇到的問題。不是很全面,希望對你有幫助。

1.專案使用了 CocoaPods 之後,為什麼要以 Workspace 形式開啟

因為執行 pod install 之後,下載完的檔案會通過使用 CocoaPods/Xcodeproj 合成一個 Project。Xcode 通過使用 Workspace 管理多個 Project,使各個 Project 之間可以相互引用。為了使工程中的檔案能夠引用元件中的檔案,所以這裡需要以 Workspace 形式開啟。

2.pod install vs. pod update

這是 官方文件 中描述的一個經典問題。

pod install: 優先安裝 Podfile 中改變的元件,並優先遵循 Podfile 中的版本號,其次遵循 Podfile.lock 中的版本號。如果使用的 Podfile 中版本號,會將新的版本號更新到 Podfile.lock 中。

pod update [PODNAME]: 會根據當前 Podfile 規則更新元件。如果 Podfile 中沒有指定版本號,並不會遵循 Podfile.lock,而是會拉取最新版本,並更新 Podfile.lock。

官方建議:

  • 新新增一個 pod 時,使用 pod install,不要使用 pod update 去下載一個新的元件,避免跟新其他 pod 的版本。
  • 更新 pod 版本時,使用 pod update [PODNAME]
  • 沒有必要的話,不要使用全域性更新 pod update,避免不必要的更新。

3.校驗 podspec 檔案出現問題(pod spec lint)

swift 版本問題

問題:

swift_error

解決方案:

2.3, run終端輸入:echo “2.3” > .swift-version

驗證出現警告問題

pod spec lint xxx.podspec --allow-warning
複製程式碼

找不到標頭檔案

pod spec lint --allow-warnings --use-libraries
複製程式碼

當然 CocoaPods 還有很多問題,這裡就不一一列舉了,如果遇到問題自行 Google 吧,很多問題都已經有了答案。

總結

CocoaPods 的相關知識,就總結到這裡。花時間如仔細研究一下,還是能學到很多東西的。這樣在今後的專案開發中遇到問題後,可以快速定位並解決,提高開效率。

參考文獻

1.CocoaPods 官方文件
2.CocoaPods Under The Hood


相關文章