升级 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
预期观察点包括:
- Xcode 能够解析新增的 Swift 包;
FlutterGeneratedPluginSwiftPackage出现在目标依赖中;- 原生插件注册阶段没有重复模块或缺少模块;
- 暂不兼容插件仍能通过 CocoaPods 进入构建;
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 上:
- SwiftPM 包是否能解析;
- CocoaPods 回退是否正常;
- Xcode Release 编译是否通过;
- Archive 是否包含正确的原生模块;
- IPA 是否能上传到 App Store Connect;
- 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 文件”,而是恢复最近一次可发布链路。建议按以下顺序操作:
- 停止修改生产分支,保留失败构建日志;
- 切回迁移前 Git 分支或恢复已标记的发布提交;
- 恢复
pubspec.lock、Podfile.lock和手写 Podfile 配置; - 在同一台 Mac 上重新执行原来的 Release Archive;
- 使用旧链路上传一个新的构建号,确认发布能力恢复;
- 将失败插件、错误阶段和回滚结果记录到迁移表;
- 只有在单个问题被复现并修复后,才重新进入双轨验证。
如果需要临时关闭 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 单轨方案。