MiniDebug 工程筆記

在雲端 Mac 建立 iOS App 體積回歸門檻

在雲端 Mac 建立 iOS App 體積回歸門檻

一次看似普通的功能合併,卻讓封存產物突然增加十幾 MB,但提交紀錄中既沒有大型圖片,也找不到明確新增的相依套件。如果只在發佈前手動檢查 IPA,問題通常早已混入多個分支。更穩妥的做法,是在固定的雲端 Mac 建置環境中保存體積基準,並在每次封存後自動拆分量測 App、主要執行檔與動態框架;只要超過門檻,就停止合併。

先定義可比較的量測對象

「套件體積」至少有三種衡量方式,不能混在同一條趨勢線上。

指標 量測對象 主要用途
App 展開體積 .xcarchive 內的 .app 目錄 找出資源、框架與本地化檔案的增長
主要執行檔體積 CFBundleExecutable 指向的 Mach-O 檔案 找出程式碼、靜態函式庫與符號的變化
IPA 體積 匯出後的壓縮檔案 觀察使用者實際下載套件的趨勢

門禁應以前兩項為主。IPA 會受到壓縮率與檔案排列方式影響,即使是同一批資源的微小變動,也可能放大或縮小壓縮結果。偵錯符號位於封存檔的 dSYMs 目錄中,不應計入使用者安裝套件,但仍應另外封存,以便後續定位問題。

只有在建置條件一致時,體積差異才具有解釋價值。Xcode 路徑、組態、目標平台、匯出方式與編譯選項都必須固定。

使用固定命令產生封存檔

先在乾淨的工作目錄中解析相依套件,再針對實體裝置目標產生 Release 封存檔。工作區名稱與 Scheme 透過任務參數傳入,避免將專案細節硬編碼在指令碼中。

set -euo pipefail

WORKSPACE="${WORKSPACE:?missing WORKSPACE}"
SCHEME="${SCHEME:?missing SCHEME}"
OUT="${OUT:-$PWD/build-size}"
ARCHIVE="$OUT/App.xcarchive"

rm -rf "$OUT"
mkdir -p "$OUT"

xcodebuild \
  -workspace "$WORKSPACE" \
  -scheme "$SCHEME" \
  -configuration Release \
  -destination 'generic/platform=iOS' \
  -archivePath "$ARCHIVE" \
  clean archive \
  CODE_SIGNING_ALLOWED=NO

如果專案在封存階段必須執行與簽章相關的指令碼,就不要強制停用簽章,而應沿用專案原本受控的組態。門禁的重點是「與正式建置保持一致」,而不是追求一條適用於所有儲存庫的命令。首次導入時,也要確認 Release 組態沒有誤用測試資源、診斷函式庫或開發伺服器位址。

拆分 App、執行檔與框架

以下指令碼會從封存檔中找出唯一的 App、讀取主要執行檔名稱,並輸出可供機器解析的 TSV。du -sk 適合持續比較目錄占用空間,stat 則用來取得主要執行檔的精確位元組數。

set -euo pipefail

ARCHIVE="${1:?usage: measure.sh path/to/App.xcarchive}"
APP_ROOT="$ARCHIVE/Products/Applications"
APP_PATH="$(find "$APP_ROOT" -maxdepth 1 -type d -name '*.app' -print -quit)"

test -n "$APP_PATH"
EXECUTABLE="$(/usr/libexec/PlistBuddy \
  -c 'Print :CFBundleExecutable' "$APP_PATH/Info.plist")"

printf "kind	name	bytes
"
APP_KB="$(du -sk "$APP_PATH" | awk '{print $1}')"
printf "app	%s	%s
" "$(basename "$APP_PATH")" "$((APP_KB * 1024))"
printf "executable	%s	%s
" "$EXECUTABLE" \
  "$(stat -f '%z' "$APP_PATH/$EXECUTABLE")"

FRAMEWORKS="$APP_PATH/Frameworks"
if test -d "$FRAMEWORKS"; then
  find "$FRAMEWORKS" -maxdepth 1 -type d -name '*.framework' -print0 |
  while IFS= read -r -d '' item; do
    kb="$(du -sk "$item" | awk '{print $1}')"
    printf "framework	%s	%s
" "$(basename "$item")" "$((kb * 1024))"
  done
fi

報告應保存為本次建置的產物,而不是只輸出一個總數。發生回歸時,審查者可以直接判斷增長來自主要執行檔、某個框架,或整個 App 目錄中的資源。

進一步細分資源目錄

如果 App 總量增加,但主要執行檔與框架維持穩定,可以分別對 Assets.car、本地化目錄、離線資料檔案與媒體資源執行 statdu。不要直接刪除看似重複的檔案;應先確認它們是否由不同 target、隨選資源規則或本地化流程產生。

使用雙門檻減少誤報

只使用百分比會讓小型元件過度敏感,只使用固定的位元組數,又可能忽略大型 App 持續膨脹的問題。可以將允許的增量定義為:

allowed = max(8 MiB, baseline_app_bytes × 3%)
failed  = current_app_bytes - baseline_app_bytes > allowed

這裡的 8 MiB 與 3% 只是導入時的初始值,並非通用標準。先記錄數次正常封存的波動,再依專案情況調整。主要執行檔與個別框架應採用更小的獨立門檻,否則新增的相依套件可能會被 App 總量掩蓋。

基準檔案應與原始碼一同審查,但不能由失敗的任務自動覆寫。合理的流程是:由任務輸出差異、開發者說明原因、審查者確認變更符合需求,最後在同一項變更中更新基準。如此才能區分「經過認可的增長」與「為了讓檢查通過而重設數字」。

排查常見的異常增長

主要執行檔增長時,先檢查靜態相依套件、泛型實體化、重複連結與編譯條件;框架增長時,核對相依套件版本與嵌入方式;資源增長時,檢查原始圖片、字型、音訊與視訊檔案,以及重複的本地化內容。

另外還要避開四個常見誤區:

  1. 直接比較不同 Xcode 環境產生的結果。
  2. 混用模擬器建置與實體裝置封存檔。
  3. 只保存總體積,不保存元件明細與提交識別碼。
  4. 門禁失敗後自動提高門檻或覆寫基準。

最終報告至少應包含提交識別碼、封存組態、App 總量、主要執行檔大小、體積最大的幾個框架、相對於基準的增量,以及判定結果。即使門禁通過,也要保留報告,才能觀察緩慢但持續的增長趨勢;門禁失敗時,則應保留封存檔與明細,在同一環境中重新檢查,而不是依賴開發者在本機重新建置。

常見問題

為什麼不能只比較最終 IPA 檔案大小?

IPA 採用壓縮格式,結果會受到資源內容、檔案順序與封裝工具影響。門檻應以未壓縮 App 目錄和主要執行檔為核心,IPA 僅作輔助觀察。

App 體積回歸門檻應該設定多少?

先收集數次正常發佈的波動資料,再同時設定絕對增量與相對增幅。範例中的 8 MiB 和 3% 是起始值,實際數字應依專案歷史調整。

相依套件升級造成體積增加時可以直接更新基準嗎?

不應直接覆寫。先確認新增符號、資源或框架符合預期,記錄原因並完成審查,再將新基準與該次變更一併提交。

獨享實體 Mac

把下一次 iOS 建置放到 MiniDebug M4 上。

固定提供 M4、16GB RAM 與 256GB SSD,可按日、週、月或季租用,並可從新加坡、日本東京、韓國首爾、香港、美国東部五個節點中選擇。實際可用狀態以控制台即時回傳為準。

選擇節點並訂購