Issue #135

CocoaPods 正在退场,SwiftPM 才刚到第二章

Cover for Weekly Issue 135

谷歌近期宣布,从下一个 Flutter 稳定版 3.44 开始,Swift Package Manager 将在默认路径上取代 CocoaPods,成为 iOS 和 macOS 应用的默认依赖管理器。CocoaPods 的 Trunk 仓库计划于 2026 年 12 月 2 日正式进入只读状态——这个时间点我们在 2024 年的周报中就讨论过了,但当 Flutter 真正开始在默认路径上用 SPM 替换 CocoaPods 时,还是引发了社区的广泛热议。

不少长期受制于 CocoaPods 的开发者,尤其是前端背景的开发者,对此表达了极大的喜悦。毕竟,他们终于可以少在 M 系列 Mac 上折腾 gem install,也不用再面对那些来路不明、却又总能精准卡住构建流程的 Pods 报错了。这场更新甚至一度演变成了一场“摆脱 Ruby 的狂欢”。不过,由于 Flutter 这次官方切换略显突然,不少第三方插件作者也感到了焦虑,要求适配 Package.swift 的 Issue 迅速涌入了不少开源库。

相较于插件作者的短期适配压力,本次切换更长期的考验其实在企业级项目的 CI/CD 重构上。对于那些底层仍带有大量 CocoaPods 历史依赖的团队来说,如何平滑、尽可能低痛地切换到 SPM,将成为基建层面的一项重大挑战。迁移并不是只在本地多写一个 Package.swift,而是意味着构建缓存、私有依赖、二进制框架、Xcode 工程生成、静态/动态链接策略以及多平台构建脚本都要跟着重新校准。

不仅是 Flutter,考虑到 CocoaPods 曾长期充当跨端框架与 iOS 原生生态之间的“万能胶水”,React Native、KMP 等社区也已经开始铺设向 SPM 迁移的路径,Unity 相关的 iOS 依赖解析链路也开始支持 SPM。接下来一段时间,围绕 Apple 平台构建链路的跨端社区,大概率会经历一场阵痛式的“大迁徙”。可以说,到 2027 年,SPM 这个已经面世十年的工具,将大概率完成对 Apple 平台主流依赖管理路径的“大一统”。

对于 SPM 而言,这无疑是一个巨大的胜利。但我们也必须看清,这并不代表 SPM 已经实现了真正意义上的“破圈”。它更像是苹果原生生态的一场“内卷式胜利”:用一套对 Swift 支持更完整、与 Xcode 集成更紧密的现代工具,替换掉了属于 Objective-C 时代的旧有方案。只要还想在 Apple 平台上稳定交付,跨平台框架最终都很难绕开苹果的原生规则。

那么,SPM 的下一个十年会走向哪里?是继续做苹果生态的“内部最优解”,还是真正走出围墙,和 Cargo、npm 这样的工具站到同一个竞争舞台上?答案恐怕并不在 SPM 自身,而在 Swift 语言能不能在 Linux、Android、嵌入式等场景里跑出真正的杀手级用例。

在那一天到来之前,我们能确定的只有一件事:CocoaPods 的退场已经不可逆,但 SwiftPM 的故事,才刚刚翻到第二章。

近期推荐

警惕 AI 编程时代的“浅层思考” (Deep Understanding while using LLMs)

Chris Eidhof 会在其 SwiftUI workshop 开始时告知学员,练习期间请暂时放下 LLM。原因并不是他反对 AI,而是通过 LLM 快速得到答案,并不等于真正理解问题。这篇文章讨论的核心,是当前 AI 编程时代一个越来越明显的现象:越来越多开发者开始陷入“浅层思考”。不断 prompt、不断修补、不断推进功能,却在不知不觉中失去对系统本身的真实理解。一旦项目复杂度上升,问题便开始失控。

本文的副标题 “Solve It in 20 Minutes — Or Actually Understand It”,不只是对 workshop 场景的描述,也像是 AI 编程时代摆在开发者面前的一道选择题。LLM 并不会替代工程能力,它只会放大开发者已有的理解与判断。


巧用 npm 全局安装 Swift 脚本 (Installing Swift scripts as global commands with npm)

越来越多 Swift 开发者开始用 Swift 编写 CLI 工具。但在开发过程中,每次修改后都需要重新编译、手动移动 binary,或维护 shell alias,很容易打断迭代节奏。Cristian Felipe Patiño Rojas 给出了一个很有创意的解决方案:借助 npm 的 bin 机制,将带有 shebang 的 Swift 脚本注册为全局命令。这样一来,Swift 脚本可以像普通 CLI 工具一样直接调用,而 npm 只负责创建全局软链接,并不参与 Swift 代码的构建过程。

考虑到通过 swift 启动脚本的冷启动开销并不小,通常可能需要数秒,该方案更适合用于 Swift CLI 的开发阶段。对于这类小工具,开发者并不一定要一开始就做成完整 Swift Package。先用最轻的方式跑起来,等工具稳定后再编译和分发,往往更加符合实际工作流。


弃用 WatchConnectivity:打造高可靠的 Watch 通信方案 (WatchConnectivity was failing 40% of the time. So I stopped using it.)

在为 Apple Watch app 构建实时通信能力时,Tarek Sabry 遇到了一个很令人头疼的问题:官方的 WatchConnectivity 并不稳定,连接成功率只有约 60%。最终,Tarek 选择不再把 WatchConnectivity 作为唯一可靠通道,而是引入一套并行的网络传输路径:用 BLE 做服务发现、HTTP 做数据传输、SSE 建立推送通道,再配合 frame ID、ack 确认、去重和重传机制。成功地将连接成功率从 60% 提升到 99%,效果几乎是立竿见影。

更有意思的是这个方案的副产品:由于完全基于标准网络协议,Watch 并不知道另一端是 iPhone、Android 手机,还是任意带 IP 地址的设备。据作者称,截至 2026 年 4 月,这是公开可用的唯一一个 Android ↔ Apple Watch 通信解决方案。该方案已开源,命名为 WatchLink,支持 Swift 6,无第三方依赖。


SwiftUI 离极致原生的 Mac 应用还有多远? (Using SwiftUI to Build a Mac-assed App in 2026)

“Mac-assed app” 一词描述的是那种真正属于 Mac 的软件:使用系统原生控件、深度集成系统功能,每一个交互细节都符合平台惯例。Paulo Andrade 在使用纯 SwiftUI 开发 macOS 应用时发现,尽管 SwiftUI 提供了足以覆盖简单场景的 API,但当开发者试图还原 Mac 应用几十年积累下来的交互惯例时,框架本身反而常常成了阻碍。

文章拆解了当前 SwiftUI 在 macOS 上的几个明显短板:自定义列表难以完整还原选中、失焦与右键上下文目标之间的细微状态差异;右键菜单打开时无法感知,导致开发者难以正确高亮菜单作用对象;拖拽过程中的状态可见性几乎为零;键盘导航在 TextField 获焦后很容易被“吞掉”;工具栏的布局控制能力也远不及 AppKit 精细。Paulo 认为,SwiftUI 仍未完成对 AppKit 的交接。


SwiftUI 后台应用刷新的调度与处理指南 (Scheduling and handling background app refresh in SwiftUI)

iOS 中的后台刷新功能看似 API 简单,但实际很容易踩坑。Natalia Panferova 在本文中演示了如何在 SwiftUI App 生命周期下使用 Background Tasks framework 配置 BGAppRefreshTaskRequest,并通过 backgroundTask(_:action:) scene modifier 注册处理逻辑。文章从启用 Background Modes、在 Info.plist 中登记 task identifier,到提交后台刷新请求和处理系统唤醒,完整串起了 SwiftUI 应用接入后台刷新的基本流程。作者还介绍了如何通过 Xcode 调试器命令在真机上模拟后台任务触发,便于开发阶段验证逻辑。

值得注意的是,后台刷新并不是一个可精确调度的定时器。即使设置了 earliestBeginDate,系统也会根据电量、用户使用习惯等因素自行决定是否以及何时唤醒应用,因此关键逻辑不应依赖它准时执行。


Swift 6 并发运行时崩溃避坑指南 (How to avoid Swift 6 concurrency crashes)

Swift 6 严格并发模式下即便编译期零警告,也并不等于运行期安全。编译器会在 actor 边界和 GCD 边界注入动态隔离断言,一旦执行路径与编译器的假设不符,仍可能在生产环境中触发 _dispatch_assert_queue_fail_swift_task_checkIsolatedSwift 崩溃。

Khoa 列举了几类常见触发场景:@MainActor 上下文中定义的闭包会继承主 actor 隔离,若随后被旧式 callback 或 queue API 在后台线程调用,便可能触发崩溃;Combine pipeline 中 receive(on:) 的位置顺序会影响闭包的隔离继承;delegate 方法若因整个类标注了 @MainActor 而继承主 actor 隔离,但 SDK 实际从内部队列回调,同样会触发运行时断言。

Khoa 在本文中介绍的方法,能帮助开发者快速定位并修复旧项目迁移 Swift 6 时常见的运行时崩溃点。不过,如果精力许可,更理想的方向仍然是按照 Swift 6 现代并发的构型重新组织功能代码,明确隔离边界,减少对局部标注和补丁式修复的依赖。

工具

SwiftMetalNumerics:面向 Apple Silicon 的 Swift 原生 GPU 数值计算库

在 Apple 平台上做实时音频分析或端侧信号建模时,开发者常会卡在一个尴尬位置:SoundAnalysis、AVAudioEngine 这类高层 API 足够方便,但很多时候像黑箱,只给整理好的结果;而当你需要自己控制 STFT、频率分桶、矩阵运算,甚至把特征继续送入自定义神经网络层时,又往往要回到 Accelerate/vDSP 和底层 Metal 之间来回衔接。CPU 侧计算很快,但一旦后续计算进入 GPU 管线,数据搬运和同步就可能成为实时处理的瓶颈。

Bugra Acemoglu 开发的 SwiftMetalNumerics 正是瞄准这个中间地带的一个新项目。它尝试用 Swift API 封装 Apple Silicon 上的 Metal / MPS / MPSGraph 与 Accelerate / LAPACK,把矩阵计算、FFT/STFT、卷积和基础神经网络层放进同一套数值计算接口中。它真正有意思的地方不只是“GPU 加速”,而是利用统一内存架构,尽量减少 DSP、矩阵计算和轻量 ML pipeline 之间的来回拷贝。

SwiftMetalNumerics 并不是 Accelerate 的全面替代品。小规模、单次计算,CPU 路径往往仍然更快;但如果你的任务是一条较长的实时信号处理或端侧推理管线,SwiftMetalNumerics 展示了一个值得关注的方向:用更 Swift、更 Apple Silicon-native 的方式,把原始数学控制权和 GPU 计算能力连接起来。


SwiftUI Preview Runner:面向自定义工具与 AI 工作流的 SwiftUI 预览引擎

SwiftUI Preview Runner 是一个很有启发性的实验项目,由 Aryan Rogye 开发。它并不是调用模拟器,也不是复刻 Xcode Preview 的 XPC 预览机制,而是把一段 SwiftUI 代码写入临时 Swift Package,编译成动态库,再通过 dlopen 动态加载,并在宿主 macOS App 中用 NSHostingView 渲染出来。

因此,它更像是一个可嵌入自有工具链的“SwiftUI Playground”:你可以把编辑器、AI 生成、MCP validator 或其他自动化流程接到它前面,让生成出来的 SwiftUI 代码立即进入“能否编译、能否渲染”的反馈环。

需要注意的是:该项目的渲染目标是 macOS SwiftUI,不是 iOS 模拟器;编译会带来延迟;动态加载代码也意味着安全边界必须非常谨慎。尽管如此,作为“如何让 AI 生成的 SwiftUI 获得真实运行反馈”的探索样本,它值得关注。

相关周报

订阅 Fatbobman 周报

每周精选 Swift 与 SwiftUI 开发技巧,加入众多开发者的行列。

立即订阅