Flutter 3.47 iOS 升级不要直接覆盖生产构建节点,也不要一次性删除 CocoaPods 配置;本周应先准备一台隔离的远程 Mac,固定 Flutter、Xcode 和依赖锁定状态,完成真机、Archive、签名导出及 CI 回滚演练后,再切换主节点。

适合维护原生 iOS 插件、Flavor、自定义 Target 的 Flutter 开发者,也适合负责远程 Mac 构建节点和 CI/CD 流水线的 DevOps 工程师。维护 Flutter add-to-app 项目的原生 iOS 工程师,则需要重点关注 Swift Package Manager 与旧有 CocoaPods 或嵌入 Framework 集成是否发生冲突。

⚠️ 截至 2026 年 8 月 21 日,Flutter 官方稳定版索引已列出 Flutter 3.47.0;官方同时说明,从 Flutter 3.44 起,Swift Package Manager 成为 iOS 与 macOS 原生依赖的默认管理方式。具体插件兼容性仍应以当前项目的构建日志和官方文档为准。查看 Flutter 稳定版发布索引

01

先按工程形态分流,而不是先改 Podfile

Flutter 3.47 iOS 升级的关键并不是“把 CocoaPods 替换成 Swift Package Manager”,而是确认当前工程到底属于哪一种迁移场景。项目是否有原生插件、多个 Flavor、自定义 Target 或 add-to-app 集成,会直接改变迁移文件、验收范围和回滚方式。

工程场景 升级后重点观察 必须保留的回退依据 适合的退出选择
接近默认模板的标准 Flutter iOS 项目 project.pbxproj、共享 Scheme、Swift 包依赖、准备脚本 升级前提交、构建日志、旧归档 通过完整验收后切换
仍含 CocoaPods 插件的混合项目 插件来源、解析结果、Pod 安装结果、最低系统要求 Podfile、锁定文件、插件版本记录 继续双轨或替换插件
多 Flavor、自定义 Target 每个 Scheme 的配置、目标依赖和 Archive 结果 各 Flavor 的 Scheme 与产物 只切换已全部通过的配置
add-to-app 原生主工程 Flutter 模块、原生包依赖、资源和初始化流程 旧集成分支、旧归档、恢复步骤 按新集成方式单独迁移
远程 Mac CI 节点 非交互 SSH、签名资产、缓存、重启恢复 节点镜像或环境记录、旧流水线 通过双轨发布周期后替换

开始前至少保存以下证据:

  • pubspec.yamlpubspec.lockios/Podfileios/Podfile.lock
  • ios/Runner.xcodeproj/project.pbxproj
  • ios/Runner.xcodeproj/xcshareddata/xcschemes/ 下的共享 Scheme;
  • 当前生产流水线脚本、导出配置和最近一次成功的 Archive;
  • Flutter、Xcode、macOS、依赖解析结果及签名身份的记录;
  • 默认 Runner、staging、production 以及自定义 Target 的构建日志。

保存这些文件的目的不是为了永久保留旧配置,而是为了在迁移后回答一个具体问题:失败究竟来自 Flutter 工程变更、依赖解析、构建配置,还是远程节点环境。

02

标准项目先验证自动迁移结果

接近默认模板、没有复杂原生改造的项目,通常可以让 Flutter CLI 完成 Swift Package Manager 的自动迁移。Flutter 官方文档指出,升级到 3.44 或更高版本并运行项目后,会自动加入 Swift Package Manager 集成;对于尚未支持 Swift Package Manager 的依赖,Flutter 会回退到 CocoaPods。查看 Swift Package Manager 应用开发者指南

这不意味着升级后可以直接提交全部生成文件。应先在独立分支运行:

flutter doctor -v
flutter --version
flutter pub get
flutter run -d <device-or-simulator>

上面的 <device-or-simulator> 只是待替换的设备标识,不应照抄为固定值。命令执行后,重点不是终端出现“成功”,而是检查 Xcode 工程是否出现以下变化:

  1. 是否存在 FlutterGeneratedPluginSwiftPackage 目标依赖;
  2. 是否为当前 Scheme 执行了 Run Prepare Flutter Framework Script
  3. Swift 包是否被正确加入 Runner Target;
  4. 模拟器运行是否成功;
  5. 真机 Release 构建是否能完成;
  6. 生成的工程文件是否只包含预期差异。

如果自动迁移失败,Flutter 文档建议重点提供 ios/Runner.xcodeproj/project.pbxproj 与当前 Scheme 文件,以便定位迁移问题。实际工程中,这两个文件也是最容易产生无意配置差异的位置,应在代码审查时逐行比较,而不是直接接受 Xcode 的全部修改。

git diff -- ios/Runner.xcodeproj/project.pbxproj
git diff -- ios/Runner.xcodeproj/xcshareddata/xcschemes/
git status --short

示例输出只用于说明检查重点:

M  ios/Runner.xcodeproj/project.pbxproj
M  ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
?? ios/Flutter/ephemeral/

ios/Flutter/ephemeral/ 是否提交,必须按照项目现有的生成文件策略处理,不能因为一次构建生成了文件,就全部加入版本库。需要提交的通常是工程配置和共享 Scheme;临时生成物则应依据官方模板、团队规则和现有仓库状态判断。

完成检查后,至少形成以下闭环:模拟器运行、真机运行、Release 构建、Archive。只通过 Debug 模拟器不能证明 Swift 包依赖、签名和生产配置都已正常。

03

CocoaPods 混合依赖应逐插件核验

Flutter iOS 项目还能继续使用 CocoaPods,但判断标准不是“Podfile 是否存在”。Flutter 官方已经明确,尚未支持 Swift Package Manager 的依赖会暂时回退到 CocoaPods;因此,Swift Package Manager 与 CocoaPods 共存本身并不等于迁移失败。查看 Flutter 3.44 关于 Swift Package Manager 的官方说明

对每个原生插件建立一张迁移记录,至少包含:

  • 插件名称和当前版本;
  • 依赖由 Swift Package Manager 解析,还是由 CocoaPods 回退;
  • Xcode 工程中的目标和链接产物;
  • 最低 iOS 系统要求;
  • Debug、Release、Archive 是否分别通过;
  • 是否包含资源包、脚本阶段或额外签名要求;
  • 如果插件失败,恢复旧依赖方式需要哪些文件。

建议把依赖检查放进升级分支,而不是直接在生产节点执行:

flutter pub deps
flutter pub get
pod install

如果项目仍然需要 CocoaPods,pod install 的结果必须与 Podfile.lock 对照;如果某个插件已经切换到 Swift Package Manager,则应在 Xcode 的 Package Dependencies、Target Membership 和构建日志中确认它没有被重复链接。

经验上,最危险的不是 Podfile 还在,而是同一个原生库同时由两种依赖方式提供,导致重复符号、资源重复复制或不同构建配置得到不同解析结果。升级记录必须写清每个插件的唯一来源。

此时可以使用下面的决策条件:

  • 若所有原生插件均已通过 Swift Package Manager 解析,且真机与 Archive 都通过,则选择纯 Swift Package Manager 集成,但保留可恢复的旧分支;
  • 若只有少量插件仍依赖 CocoaPods,且混合构建、签名和归档均通过,则继续双轨,不删除 Podfile 和锁定文件;
  • 若插件在 Swift Package Manager 下无法解析,但 CocoaPods 构建稳定,则暂缓切换该插件,记录替换或升级计划;
  • 若两种方式同时引入同一依赖,或 Release Archive 出现重复链接错误,则回退到上一个可发布提交,先清理重复集成;
  • 若失败原因无法在隔离节点复现,则不能替换生产构建节点。

CocoaPods 注册表计划于 2026 年 12 月 2 日永久转为只读,这是官方文档已公布的时间安排,但它并不等于现有 Podfile 会在当天立即失效。长期维护项目应把这件事纳入依赖迁移路线,而不是把日期当作一次性强制切换点。查看 Flutter 官方 add-to-app 文档中的维护说明

04

多 Flavor 与自定义 Target 需要逐配置验收

多 Flavor 项目最常见的误判是:默认 Runner 的 Debug 能启动,于是团队认为迁移完成;但 staging 的签名、production 的 Archive 或自定义 Target 的 Flutter 资源仍可能没有接入新的准备脚本和 Swift 包依赖。

每个 Flavor 都应单独检查:

  1. Scheme 是否为共享状态,并且在 CI 节点可见;
  2. Build Configuration 是否指向正确的 Debug、Profile 或 Release 配置;
  3. Run Prepare Flutter Framework Script 是否出现在对应 Scheme 的 Build Pre-actions;
  4. FlutterGeneratedPluginSwiftPackage 是否关联到实际构建的 Target;
  5. Bundle ID、团队、签名身份和 Provisioning Profile 是否匹配;
  6. Archive 产物是否落入预期路径;
  7. 导出的 .ipa 是否来自正确的 Flavor,而不是默认 Runner。

Flutter 官方的 Swift Package Manager 指南特别指出,自定义 Xcode Target 需要按实际目标重新关联包依赖和准备脚本;多 Flavor 也不能只配置一个 Scheme。查看 Swift Package Manager 自定义 Target 配置说明

可以为每一个配置建立独立命令:

flutter build ios --release --flavor staging
flutter build ios --release --flavor production

如果项目使用原生 Xcode Scheme 或自定义脚本,也应直接执行对应的 xcodebuild 流程,避免 Flutter CLI 覆盖了实际 CI 行为:

xcodebuild \
  -workspace ios/Runner.xcworkspace \
  -scheme <shared-scheme> \
  -configuration Release \
  -destination 'generic/platform=iOS' \
  archive \
  -archivePath <archive-path>

命令中的 Scheme、Archive 路径和签名参数必须替换为项目真实值。验收记录至少包含 Scheme 名称、构建配置、完整日志、Archive 路径和导出结果;“某个 Debug 构建成功”不能作为 production 通过的证据。

Apple 的发布流程要求先生成 Archive,再从 Archive 进行 Validate、上传或导出;因此,生产 Flavor 必须把 Archive 和导出放进验收,而不能只做 flutter run查看 Apple 的 Xcode 发布流程

05

add-to-app 项目必须先拆清旧集成

Flutter add-to-app 与纯 Flutter 应用不是同一种迁移。原生 iOS 主工程可能已经通过 CocoaPods、嵌入 Framework、手工脚本或多个原生 Target 集成 Flutter 模块,直接启用新方式容易产生重复资源、重复 Framework 或初始化路径不一致。

Flutter 官方 add-to-app 指南明确要求:如果原项目已经使用 CocoaPods 或嵌入 Framework 集成,需要先移除旧集成,再按照 Swift Package Manager 方式接入,不能把两套方式未经核对地叠加。查看 Flutter add-to-app iOS 集成指南

迁移时按以下顺序检查:

  1. 记录原生主工程当前的 Podfile、Workspace、Framework 搜索路径和 Build Phases;
  2. 确认 Flutter 模块的 pubspec.lock、插件列表和生成包;
  3. 使用官方方式生成 Flutter 的 Swift Package;
  4. 将生成的包加入实际原生 Target,而不是只加入默认 Target;
  5. 检查 FlutterEngine 初始化、路由入口和资源加载;
  6. 分别验证原生页面启动 Flutter 模块、Debug 调试和 Release 构建;
  7. 恢复旧集成分支并完成一次可回退演练。

官方文档给出的 add-to-app 前提包括 Flutter 3.44 或更高版本以及 Xcode 15.0 或更高版本;这只是该集成指南的前提条件,不应直接推断为所有 Flutter 3.47 工程的唯一 Xcode 版本要求。查看 add-to-app 的官方前提与步骤

生成 Swift Package 的命令可以单独记录在迁移脚本中:

flutter build swift-package --platform ios

该命令的输出目录、主工程相对路径和提交边界,应以项目结构决定。尤其要确认新生成的包是否被生产 Target 使用,以及新增原生依赖后是否需要重新生成 Swift Package。

06

远程 Mac CI 节点要用双轨方式上线

远程 Mac 适合承担 Flutter iOS 的持续构建、归档和签名验证,但不能因为节点拥有 root 权限或可以通过 SSH 登录,就直接把它当作生产替换节点。远程环境至少存在三个隐性风险:

  • 图形会话中存在的环境变量,在非交互 SSH 会话中可能不存在;
  • 签名证书、Provisioning Profile 和钥匙串访问权限,可能只在某个登录用户下有效;
  • 缓存命中会掩盖依赖解析或生成文件问题,重启后第一次干净构建才暴露真实缺陷。

本周可以按下面的 5 步迁移验收流程执行:

第 1 步:固定工具链和依赖状态

记录 Flutter channel 与版本、Xcode 版本、macOS 版本、Dart 依赖锁定文件、Pod 锁定文件、Swift 包解析结果和 CI 脚本提交。生产节点不做第一轮迁移,先在独立远程 Mac 节点复刻同一流水线。

第 2 步:验证非交互 SSH 环境

不要只通过远程桌面打开 Xcode 运行。应使用 SSH 执行与 CI 相同的命令,并显式检查:

ssh <user>@<remote-mac> 'echo "$PATH"; flutter --version; xcodebuild -version'

输出中的 Flutter、Xcode 和 PATH 必须与预期记录一致。凭据不能写入脚本或命令行历史,应使用受控钥匙串、CI 凭据注入或短时授权方式。

第 3 步:执行干净构建和缓存构建

先清理构建目录、依赖缓存和生成物,再执行一次完整构建;随后恢复允许使用的缓存重新执行。两次结果都应记录依赖解析、插件编译、资源复制和签名阶段,不能只比较最终退出码。

第 4 步:完成真机、Archive 和导出

真机测试应覆盖实际签名身份、Bundle ID 和 Provisioning Profile。Archive 后执行导出,并保存 .xcarchive、导出产物、构建日志和签名检查结果。

Apple 文档说明,Xcode 可以通过 Archive 生成归档,再使用 xcodebuild archivexcodebuild -exportArchive 完成命令行归档和导出;因此 CI 验收应覆盖这两个阶段,而不是只验证编译。查看 Apple 的 Archive 与导出说明

第 5 步:重启节点并演练回滚

重启远程 Mac 后重新执行非交互构建,检查钥匙串、缓存目录、环境变量、Runner 权限和任务调度是否恢复。随后把 CI 指针切回旧提交,确认旧版本仍能产生可发布产物,再恢复到迁移分支。

签名资产尤其需要单独保护。Apple 提醒,导出的签名身份和私钥一旦泄露,可能被用于生成看似来自团队的已签名软件;远程 Mac 上不应把证书、私钥和密码以明文放在仓库或普通环境变量中。查看 Apple 的签名身份管理文档

最终决策应满足以下条件:

  • 若标准项目、所有生产 Flavor、真机、Archive、导出和 SSH 非交互构建均通过,则可以安排主节点切换;
  • 若只有部分插件支持 Swift Package Manager,但双轨构建稳定,则保留 CocoaPods 回退并继续观察;
  • 若 add-to-app 旧集成尚未完成恢复演练,则不切换生产节点;
  • 若重启后签名、缓存或环境变量丢失,则先修复节点初始化;
  • 若任何生产配置无法生成可验证的 Archive,则回滚,不以模拟器或 Debug 成功替代。
07

常见迁移问题的处理边界

Flutter 3.47 升级后是否必须删除 CocoaPods?

不必须。删除前需要确认所有插件的 Swift Package Manager 支持、解析结果和 Release Archive 都已通过;否则应保留双轨回退。Podfile 的存在只是工程历史信息,不能单独作为失败判断。

为什么升级后生成文件变多?

Swift Package Manager 集成可能更新 Xcode 工程、共享 Scheme、准备脚本和生成的本地包。应通过 git diff 判断哪些是稳定配置,哪些是临时生成物,再按照仓库规则处理。

为什么默认 Runner 成功,但 production Archive 失败?

通常是 Scheme、Build Configuration、签名身份、准备脚本或自定义 Target 没有同步。生产配置必须单独执行 Archive 和导出,不能继承默认 Runner 的成功结论。

add-to-app 能不能保留旧 Podfile 再接入 Swift Package Manager?

不能未经核对地叠加。先标出旧 Flutter 集成、Framework、资源和脚本,再按新方式迁移;对于仍需 CocoaPods 的其他原生依赖,则逐项确认它们不会重复提供 Flutter 或插件产物。

远程 Mac 只负责 CI,还需要真机验收吗?

需要。CI 节点最终要生成签名归档和可导出产物,至少应验证一次真实签名链路;如果团队把真机测试放在另一台机器上,也必须记录两台机器之间的工具链和签名差异。

如果团队当前没有备用 Mac,比较稳妥的做法是先准备一个独立的 远程 Mac 构建环境,在一个发布周期内复刻现有 Flutter iOS 流水线;这比直接改生产节点更容易保留回滚路径。对于需要长期运行 CI、定时归档或多项目并行构建的团队,也可以进一步比较 Mac mini 云算力订购方案,但是否长期保留节点,仍应以实际并发量、签名要求和维护成本决定。

08

迁移后的切换建议

当前方案如果是本地 Windows 或 Linux 主机加临时共享构建机,常见缺点是无法稳定复刻 macOS 工具链、SSH 与图形会话环境可能不一致,而且多人共用签名资产会增加排障和权限风险;如果继续在生产 Mac 上直接升级,又会把依赖迁移失败、缓存污染和回滚困难叠加在一起。

远程 Mac 的价值不在于替代所有本地开发,而在于提供一台可隔离、可重启、可通过 SSH 复现的真实 macOS 构建节点。只有当 Flutter 3.47 iOS 升级清单中的依赖解析、Flavor、add-to-app、真机签名、Archive、导出和回滚全部形成证据后,才适合决定长期保留、扩容或释放该节点。