一次看似普通的功能合併,卻讓封存產物突然增加十幾 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、本地化目錄、離線資料檔案與媒體資源執行 stat 或 du。不要直接刪除看似重複的檔案;應先確認它們是否由不同 target、隨選資源規則或本地化流程產生。
使用雙門檻減少誤報
只使用百分比會讓小型元件過度敏感,只使用固定的位元組數,又可能忽略大型 App 持續膨脹的問題。可以將允許的增量定義為:
allowed = max(8 MiB, baseline_app_bytes × 3%)
failed = current_app_bytes - baseline_app_bytes > allowed
這裡的 8 MiB 與 3% 只是導入時的初始值,並非通用標準。先記錄數次正常封存的波動,再依專案情況調整。主要執行檔與個別框架應採用更小的獨立門檻,否則新增的相依套件可能會被 App 總量掩蓋。
基準檔案應與原始碼一同審查,但不能由失敗的任務自動覆寫。合理的流程是:由任務輸出差異、開發者說明原因、審查者確認變更符合需求,最後在同一項變更中更新基準。如此才能區分「經過認可的增長」與「為了讓檢查通過而重設數字」。
排查常見的異常增長
主要執行檔增長時,先檢查靜態相依套件、泛型實體化、重複連結與編譯條件;框架增長時,核對相依套件版本與嵌入方式;資源增長時,檢查原始圖片、字型、音訊與視訊檔案,以及重複的本地化內容。
另外還要避開四個常見誤區:
- 直接比較不同 Xcode 環境產生的結果。
- 混用模擬器建置與實體裝置封存檔。
- 只保存總體積,不保存元件明細與提交識別碼。
- 門禁失敗後自動提高門檻或覆寫基準。
最終報告至少應包含提交識別碼、封存組態、App 總量、主要執行檔大小、體積最大的幾個框架、相對於基準的增量,以及判定結果。即使門禁通過,也要保留報告,才能觀察緩慢但持續的增長趨勢;門禁失敗時,則應保留封存檔與明細,在同一環境中重新檢查,而不是依賴開發者在本機重新建置。
常見問題
為什麼不能只比較最終 IPA 檔案大小?
IPA 採用壓縮格式,結果會受到資源內容、檔案順序與封裝工具影響。門檻應以未壓縮 App 目錄和主要執行檔為核心,IPA 僅作輔助觀察。
App 體積回歸門檻應該設定多少?
先收集數次正常發佈的波動資料,再同時設定絕對增量與相對增幅。範例中的 8 MiB 和 3% 是起始值,實際數字應依專案歷史調整。
相依套件升級造成體積增加時可以直接更新基準嗎?
不應直接覆寫。先確認新增符號、資源或框架符合預期,記錄原因並完成審查,再將新基準與該次變更一併提交。
把下一次 iOS 建置放到 MiniDebug M4 上。
固定提供 M4、16GB RAM 與 256GB SSD,可按日、週、月或季租用,並可從新加坡、日本東京、韓國首爾、香港、美国東部五個節點中選擇。實際可用狀態以控制台即時回傳為準。