本文做一件事
- 建立 Flutter iOS CI = 控制面 + 执行面 + 缓存面 的三平面心智模型。
- 给出 macos-latest vs 自托管 的决策矩阵,5 分钟拍板。
- 把深度实现分发到 5 个专题子文章——按你的痛点入口直达。
CI 本质模型
在看任何具体工具之前,先建立一个等式——它决定了你该改哪一层:
Flutter iOS CI =
Control Plane (GitHub Actions:触发 · 审批 · Secrets 管理)
+ Execution Plane(Mac mini M4 :iOS 原生构建 · 签名 · 环境确定性)
+ Cache Plane (本地 SSD :依赖与编译产物的持久化存储)
三个推论直接决定决策方向:
- 控制面不执行代码。 优化 workflow YAML 不会让构建变快;要降分钟数必须替换执行面。
- 执行面决定可复现性。 共享池(
macos-latest)的 Xcode 版本随 GitHub 更新;自托管机器的版本由你控制。 - 缓存面决定速度。 自托管的速度优势不来自 CPU,而来自本地 SSD 取代了每次 Job 的远程缓存上传/下载。
为什么 Flutter iOS CI 需要专用 macOS 执行面?
Flutter 可以在 Linux 上完成 Android 构建和单元测试,但 iOS 构建与 App Store 发布流程只能在 macOS 上完成——这是 Apple 工具链的硬性约束,与 Flutter 本身无关。
因此 Flutter 团队的 CI 必然面临一个架构选择:iOS 那条腿放在哪里运行?常见的三类摩擦驱动团队从 macos-latest 迁移:
- 成本:GitHub 托管 macOS runner 的计费单价显著高于 Linux,iOS 冷构建时间长,账单随构建频率快速增长。
- 环境确定性:托管镜像的 Xcode 版本随 GitHub 更新,无法固定——本地通过、CI 失败的排查成本极高。
- 构建速度:iOS 依赖体积大,托管 Runner 每次需要远程缓存;自托管本地 SSD 直接消除这个瓶颈。
「把 macOS 执行面收回可控边界」是这套架构的核心决策——控制面仍保留在 GitHub(PR 触发、Review 门禁、Secrets 管理),只把执行层换成租户独占的 Mac mini M4。
官方参考:GitHub 自托管 Runner 文档 · Flutter 持续部署指南
三层架构总览
把整条链路映射到三平面模型——遇到问题时先判断属于哪一层,再进入对应专题文章:
| 平面 | 负责什么 | 不负责什么 | 出问题的信号 |
|---|---|---|---|
| 控制面 | 触发规则、权限边界、Secrets 注入、制品上传 | 不执行任何构建代码 | Job 不触发 / 权限报错 / PR 门禁失效 |
| 执行面 | iOS 原生构建、Xcode 版本固定、签名链 | 不管触发逻辑与 Secrets 来源 | 构建环境漂移 / 签名失败 / Runner 离线 |
| 缓存面 | 依赖与编译产物的跨 Job 持久化 | 不决定构建触发与执行环境 | 热构建 ≈ 冷构建 / 磁盘告警 / 随机失败 |
任何 Flutter iOS CI 故障都能归到以上三行之一。归对平面,修复路径就清晰了。
决策矩阵:什么时候值得迁移?
自托管不是默认答案。下面的矩阵用于 5 分钟内拍板:
| 信号 | 建议保留 macos-latest | 建议迁自托管 |
|---|---|---|
| 构建频率 | 每月 < 40 次 iOS 构建 | 每月 > 40 次,或每日多次 |
| 环境稳定性 | 对 Xcode 小版本不敏感 | 需要固定 Xcode / SDK 版本 |
| 构建速度 | 热构建 < 8 分钟可接受 | 热构建持续 > 8 分钟影响迭代 |
| 发版链 | 无 App Store 签名需求 | 有 App Store / TestFlight 发版链 |
| 运维意愿 | 零运维优先 | 接受每月一次维护窗口换取可控性 |
经验法则:命中以上 3 条或以上「建议迁自托管」的信号——迁移通常在 2–4 周内回本(机器租金 vs 节省的托管 macOS 分钟费)。
专题系列:按痛点直达
本文只建立架构认知与迁移决策。每个执行层面的深度内容在独立专题文章里:
| 专题 | 回答的核心问题 | 平面 | 链接 |
|---|---|---|---|
| ① Runner 搭建 | 怎么让 Mac mini 稳定在线、可调度、安全隔离 | 执行面 | 即将发布 |
| ② 三层缓存模型 | 为什么 iOS build 快不起来,怎么用本地 SSD 修 | 缓存面 | 缓存失效深度解析 → |
| ③ 签名与发版 | 怎么在无人值守的 CI 里完成 iOS 签名与 App Store 上传 | 执行面 | codesign 与 Notarization → |
| ④ Workflow 设计 | 怎么设计 Android/iOS 拆分、路径过滤与并发策略 | 控制面 | 即将发布 |
| ⑤ 运维与稳定性 | CI 为什么会炸、Runner 离线怎么恢复、磁盘怎么不撑爆 | 执行面 + 缓存面 | 磁盘与 inode 治理 → |
按场景选入口
- 从零搭建:本文确认架构方向 → 专题①「Runner 搭建」上机 → 专题②「缓存模型」提速 → 专题③「签名」接发版链。
- Runner 已有,iOS build 慢:直接进入专题②「缓存失效深度解析」。
- 签名或 TestFlight 卡住:进入专题③「codesign 与 Notarization」。
- Runner 离线 / 构建随机失败:进入专题⑤「运维与磁盘治理」。
- 从 Bitrise 等云 CI 迁移:见「Bitrise vs 自建云 Mac Runner 决策」,有迁移成本对比表。