iOS依賴管理的平臺(tái)自動(dòng)化機(jī)制

http://www.itdecent.cn/p/7f2132284a0b
https://juejin.cn/post/6844904202813046797

現(xiàn)在我可以對(duì) iBiu 平臺(tái)的 “依賴管理”“源碼與二進(jìn)制切換” 實(shí)現(xiàn)機(jī)制進(jìn)行一次最全面、最深入的詳解。

這其中的精髓在于:iBiu 并沒有改變 CocoaPods 的底層依賴管理機(jī)制,而是通過一套上層建筑,智能地、動(dòng)態(tài)地控制 CocoaPods 的行為,從而實(shí)現(xiàn)極致的靈活性和自動(dòng)化。


一、依賴管理:中央集權(quán)式的聲明與調(diào)度

iBiu 的依賴管理可以概括為:“聲明與實(shí)現(xiàn)分離”。

1. 依賴聲明(What):Podspec 中的靜態(tài)定義

在每個(gè)組件的 .podspec 文件(如 DJAppModule.podspec)中,開發(fā)者使用標(biāo)準(zhǔn)的 s.dependency 方法聲明該組件所依賴的其他組件。

# DJAppModule.podspec
s.dependency 'DJTMasonryModule', '~> 1.0.0'
s.dependency 'DJNetworkModule', '~> 2.0.0'
s.dependency 'DJBFlutterBaseModule', '~> 2.0.0'
# ... 數(shù)十個(gè)依賴

這個(gè)階段只做一件事:定義依賴關(guān)系圖。 它聲明了“我依賴誰”以及“我能接受的版本范圍”,但完全不關(guān)心“我從哪里獲取它”(源碼還是二進(jìn)制)。這就像一份食材清單,列出了需要的菜和大致要求,但沒指定去哪個(gè)市場買。

2. 依賴實(shí)現(xiàn)(How & Where):biu Gem 的動(dòng)態(tài)決策

“從哪里獲取”這個(gè)決策被推遲到了 pod install 階段,并由 biu gem 動(dòng)態(tài)完成。biu gem 的核心作用就是一個(gè) “依賴解析器”“來源決策器”。

它的決策邏輯依賴于兩個(gè)核心輸入:

  1. 最終組件配置表:來自 iBiu Server 的權(quán)威配置,指明了某個(gè)App的某個(gè)版本最終應(yīng)該使用哪個(gè)精確版本的組件(如 DJTMasonryModule 必須用 1.0.5)。
  2. 用戶權(quán)限與環(huán)境變量:決定了當(dāng)前執(zhí)行 pod install 的環(huán)境是開發(fā)模式還是集成模式。

決策流程如下圖所示:

flowchart TD
A[Pod install 開始] --> B["biu gem 被加載<br>讀取 Podspec 依賴聲明"]
B --> C{"環(huán)境判斷<br>檢測 IBIU_CURRENT_DEV_POD"}

C -- 環(huán)境變量存在<br>開發(fā)模式 --> D
subgraph D[為單個(gè)組件生成源碼依賴]
    D1[查詢環(huán)境變量值] --> D2["生成: <br>pod 'DEV_POD', :path => '本地路徑'"]
end

C -- 環(huán)境變量不存在<br>發(fā)布/集成模式 --> E
subgraph E[為所有組件生成二進(jìn)制依賴]
    E1[向iBiu Server請(qǐng)求<br>“最終組件配置表”] --> E2[獲取表中每個(gè)組件的精確版本號(hào)] --> E3["為每個(gè)依賴生成: <br>pod 'PodName', 'exact_version'"]
end

D & E --> F["biu gem 在內(nèi)存中<br>動(dòng)態(tài)生成完整的 Podfile 內(nèi)容"]
F --> G["CocoaPods 繼續(xù)執(zhí)行<br>根據(jù)生成的指令安裝依賴"]
G --> H["安裝結(jié)果: <br>開發(fā)模式: 混合安裝<br>發(fā)布模式: 全二進(jìn)制安裝"]

這個(gè)流程完美詮釋了“智能”和“動(dòng)態(tài)”的含義。最終的依賴來源既不是寫在 Podspec 里,也不是硬編碼在 Podfile 里,而是由 biu gem 在運(yùn)行時(shí)根據(jù)具體場景智能決定的。


二、源碼與二進(jìn)制切換的完整實(shí)現(xiàn)流程

讓我們用一個(gè) concrete 的例子來貫穿整個(gè)流程,看看 DJAppModule 依賴 DJTMasonryModule 是如何在兩種模式間切換的。

場景一:CI 打包機(jī) / 發(fā)布模式(二進(jìn)制)

  1. 觸發(fā):CI 系統(tǒng)開始構(gòu)建 JDDJ-app6.1.3 版本。
  2. 環(huán)境:CI 任務(wù)中沒有設(shè)置 IBIU_CURRENT_DEV_POD 環(huán)境變量。
  3. 決策
    • biu gem 的 setup_targets('DJAppModule') 方法檢測到環(huán)境變量為空,判定為發(fā)布模式。
    • 它向 iBiu Server 請(qǐng)求 JDDJ-app 6.1.3 版本的 “最終組件配置表”
    • 從配置表中查到,DJTMasonryModule 的精確版本是 1.0.5。
  4. 生成依賴biu gem 在內(nèi)存中為 CocoaPods 生成一條指令:
    pod 'DJTMasonryModule', '1.0.5' # 來源指向私有Spec Repo中對(duì)應(yīng)的二進(jìn)制地址
    
    (這個(gè)版本的 Podspec 中的 source 字段已經(jīng)被 iBiu 發(fā)布流程修改為 http://binary-repo.ibiu.com/.../DJTMasonryModule_1.0.5.framework.zip
  5. 執(zhí)行安裝:CocoaPods 收到指令,從文件服務(wù)器下載已經(jīng)編譯好的 DJTMasonryModule.framework 二進(jìn)制包。編譯速度極快。
  6. 結(jié)果:最終打包進(jìn) App 的是二進(jìn)制框架。

場景二:開發(fā)者本地調(diào)試(源碼)

  1. 觸發(fā):開發(fā)者 小王 需要修改 DJTMasonryModule 的代碼。他在終端執(zhí)行:ibiu dev DJTMasonryModule。
  2. 環(huán)境ibiu 命令行工具會(huì)做兩件事:
    • DJTMasonryModule 的源碼克隆到本地固定目錄。
    • 在當(dāng)前 Shell 會(huì)話中設(shè)置臨時(shí)環(huán)境變量:export IBIU_CURRENT_DEV_POD='DJTMasonryModule'。
  3. 決策
    • 開發(fā)者隨后執(zhí)行 pod install。
    • biu gem 的 setup_targets('DJAppModule') 方法檢測到 IBIU_CURRENT_DEV_POD='DJTMasonryModule',判定為開發(fā)模式。
    • 它同樣會(huì)去獲取組件配置表,但對(duì)于 DJTMasonryModule 這個(gè)庫,它決定覆蓋配置表中的二進(jìn)制設(shè)置。
  4. 生成依賴biu gem 生成一條完全不同的指令:
    pod 'DJTMasonryModule', :path => '/Users/wangbingyang/.ibiu/components/DJTMasonryModule/'
    
    :path 指向剛才克隆下來的本地源碼目錄)
  5. 執(zhí)行安裝:CocoaPods 收到指令,會(huì)將本地源碼路徑以 “Development Pods” 的形式符號(hào)鏈接到 Pods 目錄下。開發(fā)者可以在 Xcode 中直接看到和修改 DJTMasonryModule 的源碼。
  6. 結(jié)果DJTMasonryModule 是源碼集成,可調(diào)試修改;而其他所有依賴(如 DJNetworkModule)仍然是二進(jìn)制集成,保證整體編譯速度不受太大影響。

三、如何規(guī)劃、設(shè)計(jì)和實(shí)現(xiàn)一個(gè)類似的 Gem

自定義一個(gè)像 biu 這樣的 Gem 是一個(gè)系統(tǒng)性的工程,它不僅僅是一個(gè) Ruby 包,更是一個(gè)與 CocoaPods 深度集成、用于實(shí)現(xiàn)特定工作流的 CLI(命令行界面)工具和框架。

第一步:明確 Gem 的核心職責(zé)

首先,要清晰地定義你的 Gem 需要做什么。從之前的分析來看,biu gem 的核心職責(zé)包括:

  1. 提供命令行工具 (CLI):例如 biu dev <pod_name> 命令來設(shè)置開發(fā)環(huán)境。
  2. 擴(kuò)展 Podfile DSL:定義像 setup_targets, makeup_pods, biu_post_install 這樣的新方法,讓 Podfile 能理解你的配置。
  3. 動(dòng)態(tài)依賴管理:根據(jù)環(huán)境變量、配置文件或遠(yuǎn)程服務(wù)器來決策每個(gè)依賴是使用源碼 (:path) 還是二進(jìn)制 (:http:podspec)。
  4. 與中央服務(wù)器通信:獲取權(quán)威的“組件配置表”,解析并應(yīng)用于當(dāng)前環(huán)境。
  5. 管理組件生命周期:輔助完成組件的創(chuàng)建、發(fā)布等流程(這部分可能更偏向于 CLI 工具)。
第二步:搭建 Gem 的基礎(chǔ)結(jié)構(gòu)

使用 bundle gem <gem_name> 命令來創(chuàng)建一個(gè)標(biāo)準(zhǔn)的 Gem 項(xiàng)目骨架。我們以創(chuàng)建一個(gè)名為 biu 的 gem 為例。

bundle gem biu
cd biu

生成的目錄結(jié)構(gòu)大致如下:

biu/
├── lib/
│   ├── biu/          # 主要命名空間模塊
│   │   └── version.rb # 版本號(hào)文件
│   └── biu.rb        # 主入口文件,定義全局模塊和加載路徑
├── exe/              # 可執(zhí)行文件目錄
│   └── biu           # CLI 命令的入口腳本
├── biu.gemspec       # Gem 的規(guī)格說明文件(依賴、元數(shù)據(jù)等)
└── Gemfile
第三步:實(shí)現(xiàn) CLI (命令行工具)

exe/biu 文件是這個(gè) Gem 的命令行入口點(diǎn)。它使用 thorcommander 這類庫來構(gòu)建復(fù)雜的命令行指令非常方便。這里以更簡單的 optparse 為例。

#!/usr/bin/env ruby
# exe/biu
require 'optparse'
require 'biu' # 引入你的主庫

options = {}
sub_commands = {}

# 定義 'dev' 子命令
OptionParser.new do |opts|
  opts.banner = "Usage: biu [command] [options]"
  sub_commands['dev'] = OptionParser.new do |dev_opts|
    dev_opts.banner = "Usage: biu dev [POD_NAME]"
    dev_opts.on("-v", "--verbose", "Run verbosely") do |v|
      options[:verbose] = v
    end
  end
end.parse!

command = ARGV.shift

case command
when 'dev'
  pod_name = ARGV.shift
  if pod_name.nil?
    puts "Please specify a pod name."
    exit 1
  end
  sub_commands['dev'].parse!
  # 調(diào)用 lib/biu/commands/dev.rb 中的邏輯
  Biu::Commands::Dev.start(pod_name, options)
when 'install'
  # 處理 install 命令...
else
  puts "Unknown command #{command}"
  exit 1
end

為了更好的結(jié)構(gòu),通常會(huì)把每個(gè)子命令的實(shí)現(xiàn)放在 lib/biu/commands/ 下。

# lib/biu/commands/dev.rb
module Biu
  module Commands
    class Dev
      def self.start(pod_name, options)
        puts "Setting up development environment for pod: #{pod_name}" if options[:verbose]

        # 1. 克隆或檢查組件源碼到本地預(yù)定目錄
        local_path = checkout_or_clone_pod(pod_name)

        # 2. 設(shè)置關(guān)鍵環(huán)境變量!
        #    這是實(shí)現(xiàn)切換的“信號(hào)”
        ENV['IBIU_CURRENT_DEV_POD'] = pod_name
        ENV['IBIU_CURRENT_DEV_POD_PATH'] = local_path

        puts "Environment variable IBIU_CURRENT_DEV_POD is now set to: #{pod_name}"
        puts "Next, please run 'pod install' in your main project directory."
      end

      private

      def self.checkout_or_clone_pod(pod_name)
        # 實(shí)現(xiàn)從 GitLab 或其他地方克隆代碼的邏輯
        # 返回本地路徑,如 "/Users/username/.biu/components/#{pod_name}"
        local_path = File.expand_path("~/.biu/components/#{pod_name}")
        # ... clone logic ...
        return local_path
      end
    end
  end
end
第四步:擴(kuò)展 Podfile DSL(核心中的核心)

這是最關(guān)鍵的一步,讓你的 Gem 能夠介入并控制 pod install 的過程。你需要?jiǎng)?chuàng)建一個(gè) CocoaPods Plugin。

lib/biu/cocoapods_plugin.rb 中:

# lib/biu/cocoapods_plugin.rb
module Biu
  module Podfile
    # 這個(gè)方法將被注入到 Podfile 的上下文中
    def setup_targets(target_name)
      # 這里的 `self` 是 Podfile 的實(shí)例上下文

      # 1. 檢查環(huán)境變量,判斷模式
      developing_pod = ENV['IBIU_CURRENT_DEV_POD']
      developing_pod_path = ENV['IBIU_CURRENT_DEV_POD_PATH']

      # 2. 獲取權(quán)威配置(這里簡化為例,實(shí)際應(yīng)從網(wǎng)絡(luò)或文件讀?。?      #    component_config 可能是一個(gè) Hash,例如:
      #    { 'DJTMasonryModule' => { version: '1.0.5', source: :binary } }
      component_config = fetch_component_configuration(target_name)

      target target_name do
        component_config.each do |pod_name, config|
          if developing_pod && pod_name == developing_pod
            # 開發(fā)模式:使用本地路徑
            pod pod_name, :path => developing_pod_path
            puts "Biu: Using LOCAL path for #{pod_name}: #{developing_pod_path}"
          else
            # 發(fā)布模式:使用二進(jìn)制版本
            # 假設(shè) config[:binary_podspec] 是遠(yuǎn)程二進(jìn)制 podspec 的地址
            # 或者 config[:version] 是版本號(hào),其對(duì)應(yīng)的Podspec里的source已是二進(jìn)制地址
            pod pod_name, config[:version]
            puts "Biu: Using BINARY version for #{pod_name}: #{config[:version]}"
          end
        end
      end
    end

    def makeup_pods(target_name)
      # 實(shí)現(xiàn)示例工程依賴配置的邏輯
      # 通常是復(fù)制主target的依賴
    end

    def biu_post_install(target_name)
      # 這里可以定義post_install鉤子
    end

    private

    def fetch_component_configuration(target_name)
      # 這里實(shí)現(xiàn)從本地文件或遠(yuǎn)程服務(wù)器獲取配置表的邏輯
      # 返回一個(gè)依賴配置的Hash
      # 例如:{ 'DJTMasonryModule' => { version: '1.0.5', source: :binary } }
      return {} # 簡化為空Hash
    end
  end
end

# 最關(guān)鍵的一步:將我們的方法注入到 Podfile 的 DSL 中
module Pod
  class Podfile
    # 這行代碼使得 Podfile 能夠識(shí)別 `setup_targets` 等方法
    include Biu::Podfile
  end
end

別忘了在主文件 lib/biu.rb 中加載這個(gè)插件:

# lib/biu.rb
require "biu/version"
require "biu/cocoapods_plugin" # 加載這個(gè)插件
module Biu
  class Error < StandardError; end
end
第五步:構(gòu)建與安裝
  1. 完善 biu.gemspec:添加依賴(如 cocoapods)和元數(shù)據(jù)。

    # biu.gemspec
    Gem::Specification.new do |spec|
      spec.name          = "biu"
      spec.version       = Biu::VERSION
      spec.authors       = ["Your Name"]
      spec.email         = ["your.email@example.com"]
      spec.summary       = "A tool for managing iOS components."
      spec.homepage      = "http://github.com/yourname/biu"
      spec.license       = "MIT"
      spec.files         = Dir["lib/**/*.rb", "exe/*", "*.md"]
      spec.bindir        = "exe"
      spec.executables   = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
      spec.require_paths = ["lib"]
      spec.add_dependency "cocoapods" # 必須依賴
      spec.add_dependency "parallel"   # 可能用于并行操作
    end
    
  2. 本地構(gòu)建與測試

    gem build biu.gemspec
    gem install ./biu-0.1.0.gem
    
  3. 發(fā)布到私有 Gem Server:在公司內(nèi)部,你會(huì)有一個(gè)私有的 RubyGems 服務(wù)器(如 Gemfury、GitLab Package Registry 或自建的 gem server)。配置好源之后,發(fā)布命令通常是:

    gem push biu-0.1.0.gem --host <your-private-gem-server-url>
    
第六步:使用你的 Gem
  1. 開發(fā)者安裝:團(tuán)隊(duì)其他成員配置好私有 Gem 源后,運(yùn)行 gem install biu 或在其項(xiàng)目的 Gemfile 中加入 gem 'biu'。
  2. 修改 Podfile:項(xiàng)目的 Podfile 就從傳統(tǒng)的寫法,變成了我們之前看到的樣子:
    require 'biu' # 引入你的gem
    source '...'
    platform :ios, '10.0'
    setup_targets('YourAppTarget') # 使用你定義的新方法
    

創(chuàng)建一個(gè)像 biu 這樣的自定義 Gem,本質(zhì)上是以下幾步:

  1. 創(chuàng)建標(biāo)準(zhǔn) Gem 骨架
  2. 構(gòu)建 CLI 工具:處理 biu dev 等命令,核心工作是設(shè)置環(huán)境變量。
  3. 創(chuàng)建 CocoaPods Plugin:這是魔法發(fā)生的地方。通過 include 將自定義方法注入到 Podfile 的上下文中,在這些方法里實(shí)現(xiàn)動(dòng)態(tài)依賴決策邏輯(檢查環(huán)境變量 -> 決定用源碼還是二進(jìn)制)。
  4. 處理依賴與發(fā)布:管理好 gem 本身的依賴(尤其是 cocoapods),并發(fā)布到私有服務(wù)器。
  5. 提供標(biāo)準(zhǔn)化的 Podfile:讓使用者只需 require ‘your_gem’ 并調(diào)用你的方法,無需關(guān)心底層復(fù)雜邏輯。

這個(gè)過程融合了 Ruby Gem 開發(fā)、CocoaPods 插件機(jī)制、元編程和命令行工具開發(fā),是一個(gè)非常高階且強(qiáng)大的實(shí)踐,能夠極大地提升團(tuán)隊(duì)的工作流自動(dòng)化水平。


總結(jié):與 CocoaPods 的強(qiáng)關(guān)聯(lián)

iBiu 與 CocoaPods 的關(guān)聯(lián)是 “控制” 而非 “重構(gòu)”

  1. CocoaPods 作為底層引擎:iBiu 100% 依賴 CocoaPods 來完成依賴解析、下載、項(xiàng)目文件生成、符號(hào)鏈接管理等所有臟活累活。它是堅(jiān)實(shí)可靠的“執(zhí)行者”。
  2. biu Gem 作為智能大腦:iBiu 通過 biu gem 扮演了“大腦”的角色。它接管了本應(yīng)寫在 Podfile 中的依賴來源定義權(quán),使其從一個(gè)靜態(tài)配置文件變成了一個(gè)動(dòng)態(tài)生成的指令集
  3. 關(guān)鍵擴(kuò)展點(diǎn)biu gem 通過 Ruby 的元編程能力,在 pod install 的執(zhí)行過程中介入,通過 setup_targets 等自定義方法動(dòng)態(tài)修改了依賴安裝的參數(shù),從而實(shí)現(xiàn)了無縫切換。

這種架構(gòu)的優(yōu)勢(shì)是巨大的:

  • 對(duì)開發(fā)者透明:開發(fā)者無需關(guān)心復(fù)雜的 Podfile 語法,只需在 iBiu 工具上勾選即可。
  • 一致性:所有人的依賴來源都由同一套平臺(tái)規(guī)則決定,徹底消除了“在我電腦上是好的”這類環(huán)境問題。
  • 靈活性:一鍵切換開發(fā)/發(fā)布模式,極大提升了開發(fā)調(diào)試效率。
  • 可管理性:版本控制、權(quán)限控制、二進(jìn)制發(fā)布等全部流程化、自動(dòng)化。

最終,iBiu 平臺(tái)通過這套機(jī)制,將 CocoaPods 的強(qiáng)大功能與企業(yè)級(jí)的組件化管理需求完美地結(jié)合在了一起,成為了支撐超大型 iOS 項(xiàng)目敏捷開發(fā)的基石。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容