6. PodSpec 管理策略

本文目录

引子

本文是 Core 的最后一篇,它与另外两篇文章「Podfile 解析逻辑」「PodSpec 文件分析」共同支撑起 CocoaPods 世界的骨架。

CocoaPods-Core 这个库之所以被命名为 Core 就是因为它包含了 Podfile -> Spec Repo -> PodSpec 这条完整的链路,将散落各地的依赖库连接起来并基于此骨架不断地完善功能。

从提供各种便利的命令行工具,到依赖库与主项目的自动集成,再到提供多样的 Xcode 编译配置、单元测试、资源管理等等,最终形成了我们所见的 CocoaPods。

今天我们就来聊聊 Spec Repo 这个 PodSpec 的聚合仓库以及它的演变与问题。

Source

作为 PodSpec 的聚合仓库,Spec Repo 记录着所有 pod 所发布的不同版本的 PodSpec 文件。该仓库对应到 Core 的数据结构为 Source,即为今天的主角。

整个 Source 的结构比较简单,它基本是围绕着 Git 来做文章,主要是对 PodSpec 文件进行各种查找更新操作。结构如下:

# 用于检查 spec 是否符合当前 Source 要求
require 'cocoapods-core/source/acceptor'
# 记录本地 source 的集合
require 'cocoapods-core/source/aggregate'
# 用于校验 source 的错误和警告
require 'cocoapods-core/source/health_reporter'
# source 管理器
require 'cocoapods-core/source/manager'
# source 元数据
require 'cocoapods-core/source/metadata'

module Pod
  class Source
    # 仓库默认的 Git 分支
    DEFAULT_SPECS_BRANCH = 'master'.freeze
    # 记录仓库的元数据
    attr_reader :metadata
    # 记录仓库的本地地址
    attr_reader :repo
    # repo 仓库地址 ~/.cocoapods/repos/{repo_name}
    def initialize(repo)
      @repo = Pathname(repo).expand_path
      @versions_by_name = {}
      refresh_metadata
    end
    # 读取 Git 仓库中的 remote url 或 .git 目录
    def url
      @url ||= begin
        remote = repo_git(%w(config --get remote.origin.url))
        if !remote.empty?
          remote
        elsif (repo + '.git').exist?
          "file://#{repo}/.git"
        end
      end
    end

    def type
      git? ? 'git' : 'file system'
    end
    # ...
  end
end

Source 还有两个子类 CDNSourceTrunkSource,TrunkSouce 是 CocoaPods 的默认仓库。

在版本 1.7.2 之前 Master Repo 的 URL 指向为 GitHub 的 Specs 仓库[1],这也是造成我们每次 pod installpod update 慢的原因之一。

它不仅保存了近 10 年来 PodSpec 文件同时还包括 Git 记录,再加上墙的原因,每次更新都非常痛苦。

而在 1.7.2 之后 CocoaPods 的默认 Source 终于改为了 CDN 指向,同时支持按需下载,缓解了 pod 更新和磁盘占用过大问题。

Source 的依赖关系如下:

回到 Source 来看其如何初始化的,可以看到其构造函数 #initialize(repo) 将传入的 repo 地址保存后,直接调用了 #refresh_metadata 来完成元数据的加载:

def refresh_metadata
  @metadata = Metadata.from_file(metadata_path)
end

def metadata_path
  repo + 'CocoaPods-version.yml'
end

Metadata

Metadata 是保存在 repo 目录下,名为 CocoaPods-version.yml 的文件,用于记录该 Source 所支持的 CocoaPods 的版本以及仓库的分片规则

autoload :Digest, 'digest/md5'
require 'active_support/hash_with_indifferent_access'
require 'active_support/core_ext/hash/indifferent_access'

module Pod
  class Source
    class Metadata
      # 最低可支持的 CocoaPods 版本,对应字段 `min`
      attr_reader :minimum_cocoapods_version
      # 最高可支持的 CocoaPods 版本,对应字段 `max`
      attr_reader :maximum_cocoapods_version
      # 最新 CocoaPods 版本,对应字段 `last`
      attr_reader :latest_cocoapods_version
      # 规定截取的关键字段的前缀长度和数量
      attr_reader :prefix_lengths
      # 可兼容的 CocoaPods 最新版本
      attr_reader :last_compatible_versions
      # ...
    end
  end
end

这里以笔者 ???? 环境中 Master 仓库下的 CocoaPods-version.yml 文件内容为例:

---
min: 1.0.0
last: 1.10.0.beta.1
prefix_lengths:
- 1
- 1
- 1

最低支持版本为 1.0.0,最新可用版本为 1.10.0.beta.1,以及最后这个 prefix_lengths[1, 1, 1] 的数组。那么这个 prefix_lengths 的作用是什么呢 ?

要回答这个问题,我们先来看一张 Spec Repo 的目录结构图:

再 ???? 另外一个问题,为什么 CocoaPods 生成的目录结构是这样 ?

其实在 2016 年 CocoaPods Spec 仓库下的所有文件都在同级目录,不像现在这样做了分片。这个是为了解决当时用户的吐槽:GitHub 下载慢[2],最终解决方案的结果就如你所见:将 Git 仓库进行了分片

那么问题来了,为什么分片能够提升 GitHub 下载速度?

很重要的一点是 CocoaPods 的 Spec Repo 本质上是 Git 仓库,而 Git 在做变更管理的时候,会记录目录的变更,每个子目录都会对应一个 Git model。

而当目录中的文件数量过多的时候,Git 要找出对应的变更就变得十分困难。有兴趣的同学可以查看官方说明[3]

另外再补充一点,在 Linux 中最经典的一句话是:一切皆文件,不仅普通的文件和目录,就连块设备、管道、socket 等,也都是统一交给文件系统管理的。

也就是说就算不用 Git 来管理 Specs 仓库,当目录下存在数以万计的文件时,如何高效查找目标文件也是需要考虑的问题。

备注:关于文件系统层次结构有兴趣的同学可以查看FHS 标准[4],以及这篇文章:「一口气搞懂「文件系统」,就靠这 25 张图了」</

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值