本文是 Core 的最後一篇,它與另外兩篇文章「Podfile 解析邏輯」和「PodSpec 文件分析」共同支撐起 CocoaPods 世界的骨架。CocoaPods-Core 這個庫之因此被命名爲 Core 就是由於它包含了 Podfile -> Spec Repo -> PodSpec 這條完整的鏈路,將散落各地的依賴庫鏈接起來並基於此骨架不斷地完善功能。從提供各類便利的命令行工具,到依賴庫與主項目的自動集成,再到提供多樣的 Xcode 編譯配置、單元測試、資源管理等等,最終造成了咱們所見的 CocoaPods。html
今天咱們就來聊聊 Spec Repo
這個 PodSpec
的聚合倉庫以及它的演變與問題。前端
做爲 PodSpec
的聚合倉庫,Spec Repo 記錄着全部 pod
所發佈的不一樣版本的 PodSpec
文件。該倉庫對應到 Core 的數據結構爲 Source
,即爲今天的主角。git
整個 Source
的結構比較簡單,它基本是圍繞着 Git 來作文章,主要是對 PodSpec
文件進行各類查找更新操做。結構以下:github
# 用於檢查 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
還有兩個子類 CDNSource 和 TrunkSource,TrunkSouce 是 CocoaPods 的默認倉庫。在版本 1.7.2 以前 Master Repo 的 URL 指向爲 Github 的 Specs 倉庫,這也是形成咱們每次 pod install
或 pod update
慢的緣由之一。它不只保存了近 10 年來 PodSpec 文件同時還包括 Git 記錄,再加上牆的緣由,每次更新都很是痛苦。而在 1.7.2 以後 CocoaPods 的默認 Source 終於改成了 CDN 指向,同時支持按需下載,緩解了 pod
更新和磁盤佔用過大問題。shell
Source
的依賴關係以下:npm
回到 Source
來看其如何初始化的,能夠看到其構造函數 #initialize(repo)
將傳入的 repo 地址保存後,直接調用了 #refresh_metadata
來完成元數據的加載:json
def refresh_metadata
@metadata = Metadata.from_file(metadata_path)
end
def metadata_path
repo + 'CocoaPods-version.yml'
end
複製代碼
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 下載慢,最終解決方案的結果就如你所見:將 Git 倉庫進行了分片。
那麼問題來了,爲何分片可以提高 Github 下載速度?
很重要的一點是 CocoaPods 的 Spec Repo
本質上是 Git 倉庫,而 Git 在作變動管理的時候,會記錄目錄的變動,每一個子目錄都會對應一個 Git model。而當目錄中的文件數量過多的時候,Git 要找出對應的變動就變得十分困難。有興趣的同窗能夠查看官方說明。
另外再補充一點,在 Linux 中最經典的一句話是:「一切皆文件」,不只普通的文件和目錄,就連塊設備、管道、socket 等,也都是統一交給文件系統管理的。也就是說就算不用 Git 來管理 Specs 倉庫,當目錄下存在數以萬計的文件時,如何高效查找目標文件也是須要考慮的問題。
回到 CocoaPods,如何對 Master 倉庫目錄進行分片就涉及到 Metadata 類中的關鍵方法:
def path_fragment(pod_name, version = nil)
prefixes = if prefix_lengths.empty?
[]
else
hashed = Digest::MD5.hexdigest(pod_name)
prefix_lengths.map do |length|
hashed.slice!(0, length)
end
end
prefixes.concat([pod_name, version]).compact
end
複製代碼
#path_fragment
會依據 pod_name 和 version 來生成 pod 對應的索引目錄:
prefix_lengths
對生成的摘要不斷截取指定的長度做爲文件索引。以 AFNetworking
爲例:
$ Digest::MD5.hexdigest('AFNetworking')
"a75d452377f3996bdc4b623a5df25820"
複製代碼
因爲咱們的 prefix_lengths
爲 [1, 1, 1]
數組,那麼它將會從左到右依次截取出一個字母,即: a
、7
、5
,這三個字母做爲索引目錄,它正好符合咱們 👆 目錄結構圖中 AFNetworking 的所在位置。
要找到 Podfile
中限定版本號範圍的 PodSpec
文件還須要須要最後一步,獲取當前已發佈的 Versions 列表,並經過比較 Version 得出最終所需的 PodSpec
文件。
在上一步已經過 metadata
和 pod_name
計算出 pod
所在目錄,接着就是找到 pod
目錄下的 Versions 列表:
獲取 Versions:
def versions(name)
return nil unless specs_dir
raise ArgumentError, 'No name' unless name
pod_dir = pod_path(name)
return unless pod_dir.exist?
@versions_by_name[name] ||= pod_dir.children.map do |v|
basename = v.basename.to_s
begin
Version.new(basename) if v.directory? && basename[0, 1] != '.'
rescue ArgumentError
raise Informative, 'An unexpected version directory ...'
end
end.compact.sort.reverse
end
複製代碼
該方法重點在於將 pod_dir
下的每一個目錄都轉換成爲了 Version 類型,並在最後進行了 sort 排序。
#versions
方法主要在pod search
命令中被調用,後續會介紹。
來摟一眼 Version 類:
class Version < Pod::Vendor::Gem::Version
METADATA_PATTERN = '(\+[0-9a-zA-Z\-\.]+)'
VERSION_PATTERN = "[0-9]+(\\.[0-9a-zA-Z\\-]+)*#{METADATA_PATTERN}?"
# ...
end
複製代碼
該 Version 繼承於 Gem::Version 並對其進行了擴展,實現了語義化版本號的標準,sort 排序也是基於語義化的版原本比較的,這裏咱們稍微展開一下。
語義化版本號(Semantic Versioning 簡稱:SemVer)絕對是依賴管理工具繞不開的坎。語義化的版本就是讓版本號更具語義化,能夠傳達出關於軟件自己的一些重要信息而不僅是簡單的一串數字。 咱們每次對 Pod 依賴進行更新,最後最重要的一步就是更新正確的版本號,一旦發佈出去,再要更改就比較麻煩了。
SemVer 是由 Tom Preston-Werner 發起的一個關於軟件版本號的命名規範,該做者爲 Gravatars 創辦者同時也是 GitHub 聯合創始人。
那什麼是語義化版本號有什麼特別呢 ?咱們以 AFNetworking 的 release tag 示例:
3.0.0
3.0.0-beta.1
3.0.0-beta.2
3.0.0-beta.3
3.0.1
複製代碼
這些 tags 並不是隨意遞增的,它們背後正是遵循了語義化版本的標準。
基本規則
版本格式:
主版本號.次版本號.修訂號
版本號遞增規則以下:
| Code status | Stage | Example version |
| ------------------ | ---------------------- | --------------- |
| 新品首發 | 從 1.0.0 開始 | 1.0.0 |
| 向後兼容的 BugFix | 增長補丁號 Z | 1.0.1 |
| 向後兼容的 Feature | 增長次版本號 Y | 1.1.0 |
| 向後不兼容的改動 | 增長主版本號 X | 2.0.0 |
| 重要版本的預覽版 | 補丁號後添加 alpha, rc | 2.1.0-rc.0 |
複製代碼
關於 CocoaPods 的 Version 使用描述,傳送門。
CocoaPods 在 1.7.2 版本正式將 Master 倉庫託管到 Netlify 的 CDN 上,當時關於如何支持這一特性的文章和說明鋪天蓋地,這裏仍是推薦你們看官方說明。另外,當時感覺是彷佛國內的部分 iOS 同窗都炸了,各類標題黨:什麼最完美的升級等等。
因此這裏明確一下,對於 CocoaPods 的 Master 倉庫支持了 CDN 的行爲,僅解決了兩個問題:
然而,僅僅對 PodSpec
增長了 CDN 根本沒能解決 GFW 致使的 Github 源碼校驗、更新、下載慢的問題。 只能說路漫漫其修遠兮。
PS:做爲 iOS 工程師,就常常被前端同窗 😒 。你看這 CocoaPods 也太垃圾了吧!!!一旦刪掉
Pods
目錄從新 install 就卡半天,緩存基本不生效,哪像 npm 多快 balabala ...
先來看 CDNSource 結構:
require 'cocoapods-core/source'
# ...
module Pod
class CDNSource < Source
def initialize(repo)
# 標記是否正在同步文件
@check_existing_files_for_update = false
# 記錄時間用於對比下載文件的新舊程度,以確認是否須要更新保存所下的資源
@startup_time = Time.new
# 緩存查詢過的 PodSpec 資源
@version_arrays_by_fragment_by_name = {}
super(repo)
end
def url
@url ||= File.read(repo.join('.url')).chomp.chomp('/') + '/'
end
def type
'CDN'
end
# ...
end
end
複製代碼
Source 類是基於 Github Repo 來同步更新 PodSpec
,而 CDNSource 則是基於 CDN 服務所返回的 Response,所以將 Source 類的大部分方法重寫了一個遍,具體會在 SourceManager 一節來展開。
最後看一下 TrunkSource
類:
module Pod
class TrunkSource < CDNSource
# 新版落盤後倉庫名稱
TRUNK_REPO_NAME = 'trunk'.freeze
TRUNK_REPO_URL = 'https://cdn.cocoapods.org/'.freeze
def url
@url ||= TRUNK_REPO_URL
super
end
end
end
複製代碼
核心就是重寫了返回的 url
,因爲舊版 Spec 倉庫名稱爲 master
爲了加以區分,CDN 倉庫則更名爲 trunk
。
Manager
做爲 source 的管理類,其主要任務爲 source 的添加和獲取,而對 PodSpec
文件的更新和查找行爲則交由 source 各自實現。不過因爲一個 pod
庫可能對應多個不一樣的 source,這裏又產生出 Aggregate
類來統一 PodSpec
的查詢。
它們的關係以下:
Manager 實現:
module Pod
class Source
class Manager
attr_reader :repos_dir
def initialize(repos_dir)
@repos_dir = Pathname(repos_dir).expand_path
end
def source_repos
return [] unless repos_dir.exist?
repos_dir.children.select(&:directory?).sort_by { |d| d.basename.to_s.downcase }
end
def aggregate
aggregate_with_repos(source_repos)
end
def aggregate_with_repos(repos)
sources = repos.map { |path| source_from_path(path) }
@aggregates_by_repos ||= {}
@aggregates_by_repos[repos] ||= Source::Aggregate.new(sources)
end
def all
aggregate.sources
end
# ...
end
end
end
複製代碼
Manager 類的初始化僅須要傳入當前 repos 目錄,即 ~/.cocoapods/repos
,而 Aggregate 的生成則保存 repos_dir
了目錄下的 Source,用於後續處理。
先看 Source 的生成,在 #source_from_path
中:
def source_from_path(path)
@sources_by_path ||= Hash.new do |hash, key|
hash[key] = case
when key.basename.to_s == Pod::TrunkSource::TRUNK_REPO_NAME
TrunkSource.new(key)
when (key + '.url').exist?
CDNSource.new(key)
else
Source.new(key)
end
end
@sources_by_path[path]
end
複製代碼
以 repos_dir
下的目錄名稱來區分類型,而 CDNSource 則須要確保其目錄下存在名爲 .url
的文件。同時會對生成的 source 進行緩存。
最後看 Aggregate 結構,核心就兩個 search 方法:
module Pod
class Source
class Aggregate
attr_reader :sources
def initialize(sources)
raise "Cannot initialize an aggregate with a nil source: (#{sources})" if sources.include?(nil)
@sources = sources
end
# 查詢依賴對應的 specs
def search(dependency) ... end
# 查詢某個 pod 以發佈的 specs
def search_by_name(query, full_text_search = false) ... end
# ...
end
end
複製代碼
本節咱們來談談 source 是如何添加到 repo_dir
目錄下的。
由前面的介紹可知,每一個 source 中自帶 url,在 Source 類中 url 讀取自 Git 倉庫的 remote.origin.url
或本地 .git
目錄,而在 CDNSource 中 url 則是讀取自當前目錄下的 .url
文件所保存的 URL 地址。
那 CDNSource 的 .url
文件是在何時被寫入的呢 ?
這須要從 Podfile
提及。不少老項目的 Podfile
開頭部分大都會有一行或多行 source 命令:
source 'https://github.com/CocoaPods/Specs.git'
source 'https://github.com/artsy/Specs.git'
複製代碼
用於指定項目中 PodSpec
的查找源,這些指定源最終會保存在 ~/.cocoapods/repos
目錄下的倉庫。
當敲下 pod install
命令後,在 #resolve_dependencies
階段的依賴分析中將同時完成 sources 的初始化。
# lib/cocoapods/installer/analyzer.rb
def sources
@sources ||= begin
# 省略獲取 podfile、plugins、dependencies 的 source url ...
sources = ...
result = sources.uniq.map do |source_url|
sources_manager.find_or_create_source_with_url(source_url)
end
unless plugin_sources.empty?
result.insert(0, *plugin_sources)
plugin_sources.each do |source|
sources_manager.add_source(source)
end
end
result
end
end
複製代碼
獲取 sources url 以後會經過 sources_manager
來完成 source 更新,邏輯在 CocoaPods 項目的 Manager 擴展中:
# lib/cocoapods/sources_manager.rb
module Pod
class Source
class Manager
def find_or_create_source_with_url(url)
source_with_url(url) || create_source_with_url(url)
end
def create_source_with_url(url)
name = name_for_url(url)
is_cdn = cdn_url?(url)
# ...
begin
if is_cdn
Command::Repo::AddCDN.parse([name, url]).run
else
Command::Repo::Add.parse([name, url]).run
end
rescue Informative => e
raise Informative, # ...
ensure
UI.title_level = previous_title_level
end
source = source_with_url(url)
raise "Unable to create a source with URL #{url}" unless source
source
end
# ...
end
end
end
複製代碼
查找會先調用 #source_with_url
進行緩存查詢,如未命中則會先下載 Source 倉庫,結束後重刷 aggreate 以更新 source。
# lib/cocoapods-core/source/manager.rb
def source_with_url(url)
url = canonic_url(url)
url = 'https://github.com/cocoapods/specs' if url =~ %r{github.com[:/]+cocoapods/specs}
all.find do |source|
source.url && canonic_url(source.url) == url
end
end
def canonic_url(url)
url.downcase.gsub(/\.git$/, '').gsub(%r{\/$}, '')
end
複製代碼
另外,倉庫的下載的則會經過 #cdn_url?
方法區分,最後的下載則 📦 在兩個命令類中,歸納以下:
pod repo add-cdn
命令,僅有的操做是將 url 寫入 .url
文件中。pod repo add
命令,對於普通類型的 Source 倉庫下載本質就是 git clone
操做。簡化後源的添加流程以下:
一樣在 #resolve_dependencies
的依賴仲裁階段,當 Molinillo 依賴仲裁開始前,會觸發緩存查詢 #find_cached_set
並最終調用到 Aggregate 的 #search
。完整調用棧放在 gist 上。
咱們來看看 #search
入口:
# lib/cocoapods-core/source/aggregate.rb
def search(dependency)
found_sources = sources.select { |s| s.search(dependency) }
unless found_sources.empty?
Specification::Set.new(dependency.root_name, found_sources)
end
end
複製代碼
Aggregate 先遍歷當前 sources 並進行 dependency 查找。因爲 Git 倉庫保存了完整的 PodSpecs,只要能在分片目錄下查詢到對應文件便可,最終結果會塞入 Specification::Set
返回。
Specification::Set 記錄了當前 pod 關聯的 Source,一個 pod 可能存在與多個不一樣的 Spec 倉庫 中。
CDNSource 重寫了 #search
實現:
# lib/cocoapods-core/cdn_source.rb
def search(query)
unless specs_dir
raise Informative, "Unable to find a source named: `#{name}`"
end
if query.is_a?(Dependency)
query = query.root_name
end
fragment = pod_shard_fragment(query)
ensure_versions_file_loaded(fragment)
version_arrays_by_name = @version_arrays_by_fragment_by_name[fragment] || {}
found = version_arrays_by_name[query].nil? ? nil : query
if found
set = set(query)
set if set.specification_name == query
end
end
複製代碼
邏輯兩步走:
#ensure_versions_file_loaded
檢查 all_pods_versions 文件,若是不存在會進行下載操做。Specification::Set
做爲查詢結果,並在 #specification_name
方法內完成 PodSpec
的檢查和下載。0x01 all_pods_versions 文件下載
依據前面提到的分片規則會將 pod 名稱 MD5 分割後拼成 URL。
以 AFNetworking
爲例,經 #pod_shard_fragment
分割後獲取的 fragment 爲 [a, 7, 5]
,則拼接後的 URL 爲 cdn.cocoapods.org/all_pods_ve…
AFNetworking/0.10.0/0.10.1/.../4.0.1
AppseeAnalytics/2.4.7/2.4.8/2.4.8.0/...
DynamsoftBarcodeReader/7.1.0/...
...
複製代碼
所包含的這些 pod 都是分片後獲得的相同的地址,所以會保存在同一份 all_pods_versions
中。
def ensure_versions_file_loaded(fragment)
return if !@version_arrays_by_fragment_by_name[fragment].nil? && !@check_existing_files_for_update
index_file_name = index_file_name_for_fragment(fragment)
download_file(index_file_name)
versions_raw = local_file(index_file_name, &:to_a).map(&:chomp)
@version_arrays_by_fragment_by_name[fragment] = versions_raw.reduce({}) do |hash, row|
row = row.split('/')
pod = row.shift
versions = row
hash[pod] = versions
hash
end
end
def index_file_name_for_fragment(fragment)
fragment_joined = fragment.join('_')
fragment_joined = '_' + fragment_joined unless fragment.empty?
"all_pods_versions#{fragment_joined}.txt"
end
複製代碼
另外每一份 pods_version 都會對應生成一個文件用於保存 ETag,具體會在下一節會介紹。
0x02 PodSpec 文件下載
#specification_name
將從 all_pods_versions
索引文件中找出該 pod 所發佈的版本號,依次檢查下載對應版本的 PodSpec.json
文件。
module Pod
class Specification
class Set
attr_reader :name
attr_reader :sources
def specification_name
versions_by_source.each do |source, versions|
next unless version = versions.first
return source.specification(name, version).name
end
nil
end
def versions_by_source
@versions_by_source ||= sources.each_with_object({}) do |source, result|
result[source] = source.versions(name)
end
end
# ...
end
end
end
複製代碼
繞了一圈後回到 Source 的 #versions
方法,因爲 CDN Source 不會全量下載 pod 的 PodSpec 文件,在 #version 的檢查過程會進行下載操做。
CocoaPods 還提供了命令行工具 cocoapods-search
用於已發佈的 PodSpec
查找:
$ pod search `QUERY`
複製代碼
它提供了 Web 查詢和本地查詢。本地查詢則不一樣於 #search
,它須要調用 Aggregate 的 #search_by_name
,其實現同 #search
相似,最終也會走到 Source 的 #versions 方法。
注意,Gti 倉庫的
#search_by_name
查詢仍舊爲文件查找,不會調用其#versions
方法。
pod install
執行過程若是帶上了 --repo-update
命令則在 #resolve_dependencies
階段會觸發 #update_repositories
更新 Spec 倉庫:
# lib/cocoapods/installer/analyzer.rb
def update_repositories
sources.each do |source|
if source.updateable?
sources_manager.update(source.name, true)
else
UI.message "Skipping ..."
end
end
@specs_updated = true
end
複製代碼
不過 #update
的實現邏輯在 CocoaPods 項目的 Manager 擴展中:
# lib/cocoapods/sources_managers.rb
def update(source_name = nil, show_output = false)
if source_name
sources = [updateable_source_named(source_name)]
else
sources = updateable_sources
end
changed_spec_paths = {}
# Do not perform an update if the repos dir has not been setup yet.
return unless repos_dir.exist?
File.open("#{repos_dir}/Spec_Lock", File::CREAT) do |f|
f.flock(File::LOCK_EX)
sources.each do |source|
UI.section "Updating spec repo `#{source.name}`" do
changed_source_paths = source.update(show_output)
changed_spec_paths[source] = changed_source_paths if changed_source_paths.count > 0
source.verify_compatibility!
end
end
end
update_search_index_if_needed_in_background(changed_spec_paths)
end
複製代碼
獲取指定名稱的 source,對 aggregate 返回的所有 sources 進行 filter,如未指定則 sources 全量。
挨個調用 source.update(show_output)
,注意 Git 和 CDN 倉庫的更新方式的不一樣。
Git 倉庫更新本質就是 Git 操做,即 git pull
、git checkout
命令:
def update(show_output)
return [] if unchanged_github_repo?
prev_commit_hash = git_commit_hash
update_git_repo(show_output)
@versions_by_name.clear
refresh_metadata
if version = metadata.last_compatible_version(Version.new(CORE_VERSION))
tag = "v#{version}"
CoreUI.warn "Using the ..."
repo_git(['checkout', tag])
end
diff_until_commit_hash(prev_commit_hash)
end
複製代碼
#update_git_repo
就是 git fetch
+ git reset --hard [HEAD]
的結合體,更新後會進行 cocoapods 版本兼容檢查,最終輸出 diff 信息。
Git 倉庫是能夠經過 Commit 信息來進行增量更新,那以靜態資源方式緩存的 CDN 倉庫是如何更新數據的呢 ?
像瀏覽器或本地緩存本質是利用 ETag 來進行 Cache-Control,關於 CDN 緩存能夠看這篇:傳送門。
而 ETag 就是一串字符,內容一般是數據的哈希值,由服務器返回。首次請求後會在本地緩存起來,並在後續的請求中攜帶上 ETag 來肯定緩存是否須要更新。若是 ETag 值相同,說明資源未更改,服務器會返回 304(Not Modified)響應碼。
Core 的實現也是如此,它會將各請求所對應的 ETag 以文件形式存儲:
⚠️ 注意,在這個階段 CDNSource 僅僅是更新當前目錄下的索引文件,即 all_pods_versions_x_x_x.txt
。
def update(_show_output)
@check_existing_files_for_update = true
begin
preheat_existing_files
ensure
@check_existing_files_for_update = false
end
[]
end
def preheat_existing_files
files_to_update = files_definitely_to_update + deprecated_local_podspecs - ['deprecated_podspecs.txt']
concurrent_requests_catching_errors do
loaders = files_to_update.map do |file|
download_file_async(file)
end
Promises.zip_futures_on(HYDRA_EXECUTOR, *loaders).wait!
end
end
複製代碼
CocoaPods 對於 sources 倉庫的更新也提供了命令行工具:
$ pod repo update `[NAME]`
複製代碼
其實現以下:
# lib/cocoapods/command/repo/update.rb
module Pod
class Command
class Repo < Command
class Update < Repo
def run
show_output = !config.silent?
config.sources_manager.update(@name, show_output)
exclude_repos_dir_from_backup
end
# ...
end
end
end
end
複製代碼
在命令初始化時會保存指定的 Source 倉庫名稱 @name
,接着經過 Mixin 的 config
來獲取 sources_manager
觸發更新。
最後用一張圖來收尾 CocoaPods Workflow:
最後一篇 Core 的分析文章,重點介紹了它是如何管理 PodSpec
倉庫以及 PodSpec
文件的更新和查找,總結以下:
這裏羅列了五個問題用來考察你是否已經掌握了這篇文章,若是沒有建議你加入收藏再次閱讀:
PodSpecs
的聚合類有哪些,能夠經過哪些手段來區分他們的類型 ?Aggregate
類的理解,以及它的主要做用 ?Source
類是如何更新 PodSpec
?PodSpec
文件 ?