升级 Flutter 3.47 后,iOS 工程自动出现 Swift Package Manager 文件,插件却可能仍依赖 CocoaPods,直接删除旧配置容易让 Archive 失败。

最快解法:默认接入 Swift Package Manager,暂不兼容的插件继续回退到 CocoaPods;在隔离 Mac 上完成 Archive 和 TestFlight 验收后,再改生产构建链路。

这篇文章适合从旧版 Flutter 升级到 3.47、又不想中断 iOS 发布的独立开发者;也适合依赖多个原生插件、尚未确认 Swift Package Manager 兼容性的项目维护者。
如果你主要在 Windows 或 Linux 上写代码,需要借助远程 Mac 完成构建、签名和上架,这套双轨流程同样适用。

最后更新于 2026 年 9 月 17 日,本文核实自 Flutter 3.47 发布说明、Swift Package Manager 官方迁移文档、Flutter iOS 发布文档及 Apple 的 App Store Connect 帮助页。(Flutter 官方发布说明)

迁移前的发布基线

Flutter 3.47 并不是突然才引入 Swift Package Manager。Flutter 官方说明,从 3.44 开始,Swift Package Manager 已成为 iOS 与 macOS 原生依赖的默认管理方式;如果某个依赖还不支持 SwiftPM,Flutter 会回退到 CocoaPods。也就是说,升级到 3.47 后看到工程结构变化是预期行为,并不等于 CocoaPods 立即失效。(Flutter SwiftPM 应用开发者文档)

你需要先把最近一次可以发布的状态保存下来,至少记录以下内容:

  • Flutter SDK 版本、Dart 版本和项目当前分支;
  • pubspec.lock、Podfile.lock 以及是否存在 ios/Pods/;
  • Xcode 版本、当前选择的开发者目录和最低 iOS 版本;
  • Bundle ID、签名方式、证书类型和 Provisioning Profile 来源;
  • 最近一次成功的 Release 构建、Archive 文件和可安装的 TestFlight 构建。

不要在唯一的生产打包机上直接执行迁移。更稳妥的做法是建立独立 Git 分支,并在另一台 Mac 或临时远程 Mac 上复验。若你需要先准备一个隔离的 macOS 环境,可以参考 SSHMac 的 Mac 远程使用帮助,但项目密钥、证书私钥和 App Store Connect 凭证仍应由你自己保管。

升级后的 iOS 工程变更来源

因为 Flutter 会在升级并运行项目时自动加入 SwiftPM 集成,用于获取插件声明的 Swift 包。官方文档建议在 Xcode 中确认 Run Prepare Flutter Framework Script 被设置为预操作,同时检查 FlutterGeneratedPluginSwiftPackage 是否成为目标依赖。(Flutter SwiftPM 配置要求)

这类自动修改通常会影响以下位置:

  • Xcode 工程中的 SwiftPM 集成;
  • Flutter 生成的插件包描述;
  • 原生插件注册阶段;
  • 调试与发布配置中的依赖引用;
  • Xcode 工作区和项目打开方式。

不要只用一次 Debug 构建判断迁移成功。Debug 可能没有覆盖签名、Release 编译条件、资源处理、上传脚本或某个只在真机出现的原生功能。

第一小时的插件兼容盘点

Flutter 插件并不等于纯 Dart 依赖。只要插件包含 Swift、Objective-C、原生 Framework、系统权限配置、Privacy Manifest 或自定义构建脚本,就必须单独检查其 iOS 依赖方式。Flutter 官方的插件开发文档也把 SwiftPM 作为原生 Darwin 依赖的主要策略,同时保留 CocoaPods 作为兼容路径。(Flutter 插件开发文档)

建议先在项目目录执行以下命令。示例中的账号、目录和项目名称均为脱敏占位符:

# 执行账号:构建账号;项目目录:/Users/builduser/work/app_redacted
# Flutter SDK:/Users/builduser/flutter
cd /Users/builduser/work/app_redacted

/Users/builduser/flutter/bin/flutter --version
/Users/builduser/flutter pub deps --style=compact
/Users/builduser/flutter pub get

预期结果是能够看到 Flutter 3.47.x 的 SDK 信息,依赖树可以正常解析,并且没有因为锁文件或版本约束产生新的冲突。若 pub get 失败,不要继续改 iOS 工程;先恢复原分支和原锁文件。

然后按下面的分类建立一张插件表:

插件类型 识别信号 处理建议
已支持 SwiftPM 插件文档或仓库提供 Package.swift,构建日志能完成包解析 优先纳入 SwiftPM 验证
暂不支持 SwiftPM 只有 .podspec、Podfile 依赖或 CocoaPods 安装说明 暂时保留 CocoaPods
自定义原生依赖 本地 Pod、私有 Spec、手写 Framework、构建脚本 单独验证,不要自动删除配置
来源不明或长期未维护 没有明确依赖说明,且构建日志缺少完整信息 先锁版本,必要时替换插件

插件暂不支持 SwiftPM 的处理方式

不要为了追求“全 SwiftPM”而直接修改第三方插件源码。只要该插件仍依赖 CocoaPods,就保留 CocoaPods 回退路径;同时把它登记为迁移阻塞项,并记录插件版本、Pod 名称、最低系统版本和最后一次成功 Archive。

如果插件本身已经很久没有维护,优先评估替代插件。迁移的目标是保持可发布,而不是让依赖管理器的名称看起来更现代。

首次接入 SwiftPM 的最小变更

在隔离分支上升级 Flutter 后,先获取依赖,再打开 iOS 工作区。不要一开始就删除 Podfile、Podfile.lock 或 Pods 目录,因为这些文件仍可能服务于暂不兼容的插件。

# 执行账号:builduser;项目目录:/Users/builduser/work/app_redacted
# Flutter SDK:/Users/builduser/flutter;Xcode 路径:/Applications/Xcode.app
cd /Users/builduser/work/app_redacted

sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
/Users/builduser/flutter/bin/flutter clean
/Users/builduser/flutter/bin/flutter pub get
open ios/Runner.xcworkspace

预期观察点包括:

  1. Xcode 能够解析新增的 Swift 包;
  2. FlutterGeneratedPluginSwiftPackage 出现在目标依赖中;
  3. 原生插件注册阶段没有重复模块或缺少模块;
  4. 暂不兼容插件仍能通过 CocoaPods 进入构建;
  5. flutter run 可以完成一个无签名模拟器构建或基础真机构建。

第一次验证只做无签名或开发构建,不要同时引入新的证书、Bundle ID 或发布配置。若包解析失败,撤销动作是切回迁移前分支,恢复 pubspec.lock、Podfile.lock 和 iOS 工程文件,再重新确认是哪一个插件触发问题。

SwiftPM 与 CocoaPods 的并行边界

两种方式可以在过渡期共同存在,但前提是它们负责不同的原生依赖,不能让同一个库被重复引入。Flutter 官方明确说明,不支持 SwiftPM 的依赖会回退到 CocoaPods,因此双轨本身是过渡期的正常状态。

常见冲突证据与处理方向如下:

  • 重复符号:检查同一原生库是否同时由 SwiftPM 和 Pod 引入;
  • 找不到模块:确认插件注册、Target Membership 和生成的包描述是否完整;
  • 最低系统版本冲突:统一项目部署目标,再核对插件要求;
  • 远程包下载失败:先确认网络、缓存和仓库版本,不要把缓存当成依赖来源;
  • 自定义 Podfile 逻辑失效:检查是否有手写脚本、私有源或本地 Pod。

如果你的 Podfile 除了 Flutter 插件外,还加入了自定义依赖或构建逻辑,那么即使所有 Flutter 插件都支持 SwiftPM,也不能机械删除这些配置。先清理手动加入的 CocoaPods 逻辑,再验证纯 SwiftPM 构建。

远程 Mac 上的可重复构建

Windows 或 Linux 可以承担 Dart、业务逻辑和大部分跨平台开发工作,但 iOS Release 构建仍需要 macOS 与 Xcode。Flutter 官方 iOS 发布指南也明确要求使用运行 macOS 的设备完成构建和发布。(Flutter iOS 构建发布文档)

远程 Mac 的重点不是“能打开 Xcode”,而是每次无人值守构建都使用同一套工具链。建议固定:

  • Flutter SDK 路径和版本;
  • Xcode 选择路径;
  • pubspec.lock 与原生依赖锁文件;
  • 构建账号和默认 Keychain;
  • Release 配置、导出方式和 API 凭证;
  • 远程会话中断后的重新连接和日志保存方式。

SwiftPM 缓存只能用于加速,不能代替干净环境验证。至少安排一次清理缓存后的依赖解析,确认项目在没有本机历史缓存的情况下仍能完成构建。证书私钥、Provisioning Profile 和 App Store Connect API Key 也不要与 Swift 包缓存放在同一目录,更不要写入仓库。

你可以把远程 Mac 当成独立迁移节点,而不是直接替换生产打包机。SSHMac 的 VNC 连接说明适合处理需要观察 Xcode、签名弹窗或上传状态的交互场景;无人值守构建则应保留终端日志和明确的退出码。

Windows 或 Linux 环境下的验证分工

Windows 或 Linux 可以先完成依赖树整理、锁文件提交和插件版本盘点,但以下验证必须放到 Mac 上:

  1. SwiftPM 包是否能解析;
  2. CocoaPods 回退是否正常;
  3. Xcode Release 编译是否通过;
  4. Archive 是否包含正确的原生模块;
  5. IPA 是否能上传到 App Store Connect;
  6. TestFlight 安装后,涉及相机、推送、支付或登录的功能是否正常。

远程验证时,项目名称、Bundle ID、Team ID、证书名称和日志中的用户路径都应脱敏。不要把包含私钥、API Key 或完整签名错误信息的日志直接贴到公共 issue。

Archive 与 TestFlight 发布验收

完成 Debug 或基础 Release 构建后,再进入正式验收。Flutter 官方推荐使用 flutter build ipa 生成 Xcode Archive 和 IPA,默认产物位于 build/ios/archive/ 与 build/ios/ipa/。(Flutter iOS 打包命令说明)

# 执行账号:builduser;项目目录:/Users/builduser/work/app_redacted
# Flutter SDK:/Users/builduser/flutter;输出日志:/Users/builduser/logs/app_redacted
cd /Users/builduser/work/app_redacted

/Users/builduser/flutter/bin/flutter build ipa \
  --release \
  --export-method app-store \
  2>&1 | tee /Users/builduser/logs/app_redacted/flutter-build-ipa.log

预期结果是同时得到 .xcarchive 和 .ipa。如果失败,先根据错误发生阶段分类:

  • 依赖解析阶段失败:回到插件兼容表;
  • 编译阶段失败:检查重复模块、最低系统版本和原生 API;
  • 签名阶段失败:检查证书、Profile、Bundle ID 和 Team 配置;
  • 导出阶段失败:检查 ExportOptions.plist 与发布方式;
  • 上传阶段失败:检查 API Key 权限、版本号和构建号。

Apple 规定,上传的 Bundle ID 与版本号用于关联 App Store Connect 中的应用记录,构建号则用于区分不同上传版本。你不能只看本地 IPA 生成成功,还要检查上传后的处理状态。(Apple 构建上传规则)

在 App Store Connect 中,上传后依次核对:

  • 构建号是否为本次迁移专用的新值;
  • 构建是否从处理中变为可测试状态;
  • 是否出现 Invalid Binary、缺少出口合规信息或签名问题;
  • TestFlight 安装后,原生插件功能是否可用;
  • 外部测试所需的构建信息和测试说明是否完整。

Apple 的构建状态页面把红色状态定义为需要处理的问题,黄色状态代表仍在进行中的流程,绿色状态表示构建已经进入 TestFlight 测试状态。(Apple 构建状态说明)

Archive 失败时的回滚路径

回滚目标不是“删除所有 SwiftPM 文件”,而是恢复最近一次可发布链路。建议按以下顺序操作:

  1. 停止修改生产分支,保留失败构建日志;
  2. 切回迁移前 Git 分支或恢复已标记的发布提交;
  3. 恢复 pubspec.lock、Podfile.lock 和手写 Podfile 配置;
  4. 在同一台 Mac 上重新执行原来的 Release Archive;
  5. 使用旧链路上传一个新的构建号,确认发布能力恢复;
  6. 将失败插件、错误阶段和回滚结果记录到迁移表;
  7. 只有在单个问题被复现并修复后,才重新进入双轨验证。

如果需要临时关闭 SwiftPM,Flutter 支持在项目的 pubspec.yaml 中设置 enable-swift-package-manager: false,也支持通过命令关闭当前用户的全局配置。不过官方不建议把关闭 SwiftPM 作为长期策略,因为 CocoaPods 注册表计划于 2026 年 12 月 2 日变为只读。(Flutter SwiftPM 回退配置)

三种构建策略的评分

下面的评分不是性能测试,而是针对“依赖迁移期间保持 TestFlight 发布稳定”这一目标的工程判断。分数越高,代表越适合当前迁移阶段。

方案 适用条件 发布稳定性 迁移成本 回滚难度 综合建议
立即删除 CocoaPods,全部切 SwiftPM 所有插件已有明确 SwiftPM 支持,且没有手写 Pod 配置 3/5 2/5 4/5 不建议作为第一次迁移
SwiftPM 主路径 + CocoaPods 回退 插件兼容状态不完整,需要继续发布 5/5 3/5 2/5 首选方案
暂时关闭 SwiftPM,继续全量 CocoaPods 迁移阻塞严重,当前发布窗口很紧 4/5 1/5 1/5 仅作短期回退

你可以用下面的条件分支决定下一步:

  • 若全部原生插件都有明确 SwiftPM 支持,且不存在自定义 Podfile 逻辑,则进入 SwiftPM 主路径,并保留一个可恢复的旧发布提交。
  • 若只有部分插件支持 SwiftPM,则采用双轨,禁止删除 Podfile 和 Podfile.lock。
  • 若出现重复符号、模块缺失或 Release Archive 失败,则先回退到已验证链路,不要继续修改第三方插件源码。
  • 若 Debug 通过但 Archive 失败,则问题尚未解决;只有 TestFlight 安装和关键原生功能复验通过,才允许替换生产构建机。
  • 若只有 Windows 或 Linux 开发环境,则把远程 Mac 作为隔离迁移节点,先完成双轨验收,再决定是否长期保留。

如果你还需要把远程 Mac 用作持续打包节点,可以继续查看 SSHMac 的 Mac mini 租赁说明。它更适合短期迁移、版本升级、签名复验或在没有本地 Mac 时完成 iOS 发布准备;长期高频构建则应结合构建队列、凭证管理和团队协作成本重新评估。

生产切换标准

正式切换前,至少保留一个旧依赖链路对应的可发布提交,并完成以下验收:

  • Release 构建成功;
  • Archive 成功生成;
  • IPA 导出成功;
  • App Store Connect 接收并处理构建;
  • TestFlight 可以安装;
  • 关键原生插件在真实设备上通过验证;
  • 签名、推送、登录、支付或相机等高风险功能没有回归;
  • 迁移后的构建日志、依赖锁文件和回滚步骤已经归档。

Apple 的 App Store Connect 工作流要求先创建应用记录,再上传构建;上传后可以把构建分发给内部或外部测试人员。(Apple App Store Connect 工作流) TestFlight 不是“上传成功”的同义词,构建还需要经过 Apple 处理,并在测试环境中确认实际安装和功能表现。

如果你当前只有一台生产 Mac,或主要在 Windows、Linux 上开发,直接在生产机上迁移的缺点很明确:失败会影响日常发布,缓存和账号状态难以区分,远程中断后也不容易恢复。与其把 CocoaPods 配置一次性删掉,不如临时租用 SSHMac 的独立 Mac 节点,完成 Flutter 3.47 双轨迁移、Archive 和 TestFlight 复验;等新旧链路都能稳定发布后,再决定是否长期保留 SwiftPM 单轨方案。