您的当前位置:首页正文

Cocoapods 工作原理和源码分析

来源:花图问答

前言

本文描述 Cocoapods 的工作原理和源码分析,阅读完本文后可以更好的理解在使用 Cocoapods 中遇到的问题并找到解决方法,并且有新的开发语言出现时也可以开发一款自己的包管理工具。

Cocoapods 的安装

Cocoapods 的安装非常简单,只需一行命令即可(需对系统的网络请求进行翻墙)

$ sudo gem install cocoapods

安装完后可在终端输入 pod ,会有如下输出:

pod.png

显示了 pod 的所有可用的命令和命令选项。

Cocoapods 的使用

打开终端,切换到你的工程目录,输入下面的命令

pod init

终端输入 ls 可以看到已经多了个 Podfile 文件,使用 vi 打开,内容如下

# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'SwiftDemo' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

  # Pods for SwiftDemo

  target 'SwiftDemoTests' do
    inherit! :search_paths
    # Pods for testing
  end

  target 'SwiftDemoUITests' do
    inherit! :search_paths
    # Pods for testing
  end

end

编写好 Podfile 后执行以下命令:

pod install

Cocoapods 的工作流程

pod install 执行流程可分为如下五个步骤

  • 查看 ~/.cocoapods/repo/master/Specs 是否存在
  • 存在,从这个本地三方库信息库中获取 Podfile 中对应三方库的 git 地址
  • 不存在,输出 Setting up CocoaPods Master repo,并拉取到 ~/.cocoapods/repo/中
  • 使用 git 命令从 GitHub 上拉取 Podfile 中对应的三方库源码

在终端中输入如下命令

cd ~/.cocoapods/repo/master/Specs
ls
repos.png

可以看到很多文件夹,其中就包含我们平常使用的三方库的文件夹。
随便选进入一个文件夹,这里我 cd 进了 Alamofire 文件夹。

versions.png

这里全是 Alamofire 的 release 版本号,当我们使用 pod search Alamofire 命令时会将这里的所有版本号输出出来。
cd 进最新版本的文件夹中,里面是一个 Alamofire.podspec.json 文件,使用 vi 打开,内容如下

{
"name": "Alamofire",
  "version": "4.0.0",
  "license": "MIT",
  "summary": "Elegant HTTP Networking in Swift",
  "homepage": 
  "social_media_url": 
  "authors": {
    "Alamofire Software Foundation": "info@alamofire.org"
  },
  "source": {
    "git": 
    "tag": "4.0.0"
  },
  "platforms": {
    "ios": "8.0",
    "osx": "10.9",
    "tvos": "9.0",
    "watchos": "2.0"
  },
  "source_files": "Source/*.swift"
}

可以看到这里包含了所有的三方库的相关信息,包括名字,协议,描述,Github 地址,支持平台等。

在查找到对应文件夹后,进行 json 数据解析,获得三方库的 repo 地址,调用本地 git 命令拉取源码,拉取完成后调用本地 xcodebuild 命令把三方库编译为 Framework。

源码分析

commands.png

其中 setup 命令用于第一次使用 cocoapods 时对使用环境进行设置,下面对 setup 命令的源码进行分析。

require 'fileutils'

module Pod
  class Command
    class Setup < Command
      self.summary = 'Setup the CocoaPods environment'

      self.description = <<-DESC
        Creates a directory at `~/.cocoapods/repos` which will hold your spec-repos.
        This is where it will create a clone of the public `master` spec-repo from:
            
        If the clone already exists, it will ensure that it is up-to-date.
      DESC

      extend Executable
      executable :git

      def run
        UI.section 'Setting up CocoaPods master repo' do
          if master_repo_dir.exist?
            set_master_repo_url
            set_master_repo_branch
            update_master_repo
          else
            add_master_repo
          end
        end

        UI.puts 'Setup completed'.green
      end

      #--------------------------------------#

      # @!group Setup steps

      # Sets the url of the master repo according to whether it is push.
      #
      # @return [void]
      #
      def set_master_repo_url
        Dir.chdir(master_repo_dir) do
          git('remote', 'set-url', 'origin', url)
        end
      end

      # Adds the master repo from the remote.
      #
      # @return [void]
      #
      def add_master_repo
        cmd = ['master', url, 'master']
        Repo::Add.parse(cmd).run
      end

      # Updates the master repo against the remote.
      #
      # @return [void]
      #
      def update_master_repo
        show_output = !config.silent?
        config.sources_manager.update('master', show_output)
      end

      # Sets the repo to the master branch.
      #
      # @note   This is not needed anymore as it was used for CocoaPods 0.6
      #         release candidates.
      #
      # @return [void]
      #
      def set_master_repo_branch
        Dir.chdir(master_repo_dir) do
          git %w(checkout master)
        end
      end

      #--------------------------------------#

      # @!group Private helpers

      # @return [String] the url to use according to whether push mode should
      #         be enabled.
      #
      def url
        self.class.read_only_url
      end

      # @return [String] the read only url of the master repo.
      #
      def self.read_only_url
        
      end

      # @return [Pathname] the directory of the master repo.
      #
      def master_repo_dir
        config.sources_manager.master_repo_dir
      end
    end
  end
end

其执行流程可分为如下步骤

  • 判断 ~/.cocoapods/repo目录是否存在
  • 存在,依次调用 set_master_repo_url,set_master_repo_branch,update_master_repo 三个函数分别设置 repo 主分支地址,git checkout 到主分支,拉取主分支代码,更新repo
  • 不存在,添加主分支 repo

使用 Cocoapods 的小技巧

大家在使用 pod install 命令时一般会加上 --no-repo-update 选项。这使得 pod install 不进行本地三方库信息库 git pull 的更新操作。若 Podfile 中指定 Alamofire 的版本号为 4.2.0,但本地 ~/cocoapods/repo/master/Specs/Alamofire 中并没有此版本号,此时使用 pod install --no-repo-update 会出现如下错误,

pod install.png

若直接使用 pod install 便会先执行 pod repo update,由于 github 需要翻墙,并且更新的内容过大就会等待很久。
既然知道了 Cocoapods 的原理,我们便可以手动在 ~./cocoapods/repo/master/Specs 中添加我们需要的三方库版本信息,避免了把所有的并没有使用到的三方库信息更新到本地。
下面以添加 Alamofire 4.2.0 版本信息到本地为例子。
在终端输入如下命令

cd ~/.cocoapods/repo/master/Specs/Alamofire/
mkdir 4.2.0
cp ~/.cocoapods/repo/master/Specs/Alamofire/1.1.3/Alamofire.podspec.json ./4.2.0

上面的命令创建了名为 4.2.0 的文件夹,并复制了一个库信息的 json 文件到新创建的文件夹中,用 vi 打开该文件,内容如下

{
  "name": "Alamofire",
  "version": "1.1.3",
  "license": "MIT",
  "summary": "Elegant HTTP Networking in Swift",
  "homepage": 
  "social_media_url": 
  "authors": {
    "Mattt Thompson": "m@mattt.me"
  },
  "source": {
    "git": 
    "tag": "1.1.3"
  },
  "platforms": {
    "ios": "8.0"
  },
  "source_files": "Source/*.swift",
  "requires_arc": true
}

我们需要修改版本号,source 的 tag。那 source 的 tag 怎么知道是多少呢,这个可以从 GitHub 上找到。

release version.png

可以看到所有 release 版本信息。

总结

到此,你已经知道了 Cocoapods 是调用了本地的 git 命令来实现拉取源码。当出现一门新的语言,可以按照类似逻辑开发自己的三方库管理工具。