Fatbobman's Swift Weekly #135
CocoaPods Is on Its Way Out, but SwiftPM’s Story Is Just Entering Chapter Two
CocoaPods Is on Its Way Out, but SwiftPM’s Story Is Just Entering Chapter Two
Google recently announced that, starting with the next Flutter stable release, version 3.44, Swift Package Manager will replace CocoaPods in the default workflow and become the default dependency manager for iOS and macOS apps. CocoaPods’ Trunk repository will officially become read-only on December 2, 2026 — a date we already discussed in the 2024 issues of the newsletter. Still, when Flutter truly began replacing CocoaPods with SPM in its default path, it sparked widespread discussion across the community.
Many developers who have long been constrained by CocoaPods, especially those with a front-end background, greeted the news with great enthusiasm. After all, they can finally spend less time wrestling with gem install on M-series Macs, and they no longer have to face those mysterious Pods errors that always seem to appear from nowhere yet somehow manage to block the build process with surgical precision. The update even briefly turned into something of a “celebration of breaking free from Ruby.” However, because Flutter’s official switch came somewhat abruptly, many third-party plugin authors also felt anxious, as issues requesting Package.swift support quickly began pouring into numerous open-source projects.
Compared with the short-term adaptation pressure on plugin authors, the more lasting test brought by this transition lies in the reconstruction of CI/CD workflows for enterprise projects. For teams whose foundations still contain a large number of historical CocoaPods dependencies, switching to SPM smoothly and with as little pain as possible will become a major infrastructure-level challenge. Migration is not simply a matter of adding a Package.swift file locally. It means that build caches, private dependencies, binary frameworks, Xcode project generation, static and dynamic linking strategies, and multi-platform build scripts all need to be recalibrated.
This is not limited to Flutter. Given that CocoaPods has long acted as the “universal glue” between cross-platform frameworks and the native iOS ecosystem, communities such as React Native and KMP have also begun laying the groundwork for migration to SPM, while Unity-related iOS dependency resolution workflows have also started supporting SPM. Over the next period, cross-platform communities built around Apple platform build pipelines will very likely go through a painful “great migration.” It is fair to say that by 2027, SPM — a tool that has already been around for a decade — will most likely complete its “great unification” of the mainstream dependency management path across Apple platforms.
For SPM, this is undoubtedly a major victory. But we should also be clear-eyed about what this victory means. It does not mean that SPM has achieved a true breakthrough beyond its original circle. Rather, it looks more like an inward-facing victory within Apple’s native ecosystem: replacing an older solution from the Objective-C era with a modern toolchain that offers better Swift support and tighter integration with Xcode. As long as cross-platform frameworks want to deliver reliably on Apple platforms, they will ultimately find it difficult to bypass Apple’s native rules.
So where will SPM go in its next decade? Will it continue to serve as the “internal optimal solution” for Apple’s ecosystem, or will it truly step beyond the walls and compete on the same stage as tools like Cargo and npm? The answer probably does not lie in SPM itself, but in whether the Swift language can produce truly killer use cases in areas such as Linux, Android, and embedded systems.
Until that day arrives, there is one thing we can be certain of: CocoaPods’ exit is already irreversible, but SwiftPM’s story has only just turned to its second chapter.
Previous Issue|Newsletter Archive
📢 Sponsor Fatbobman’s Swift Weekly
Promote your product to Swift & iOS developers across:
- Blog: 50,000+ monthly visitors
- Newsletter: 4,000+ subscribers, 53% open rate
Perfect for developer tools, courses, and services.
Enjoyed this issue? Buy me a coffee ☕️
Recent Recommendations
Deep Understanding while using LLMs
At the beginning of his SwiftUI workshops, Chris Eidhof tells attendees to put LLMs aside during the exercises. The reason is not that he is against AI, but that getting an answer quickly with an LLM is not the same as truly understanding the problem. The core of this article is an increasingly visible phenomenon in the age of AI-assisted programming: more and more developers are falling into “shallow thinking.” They keep prompting, patching, and pushing features forward, while gradually losing a real understanding of the system itself. Once project complexity increases, things start to spiral out of control.
The article’s subtitle, “Solve It in 20 Minutes — Or Actually Understand It,” is not only a description of the workshop setting, but also a choice that the AI programming era places in front of developers. LLMs do not replace engineering ability; they amplify the understanding and judgment that developers already have.
Installing Swift scripts as global commands with npm
More Swift developers are starting to use Swift to write CLI tools. But during development, having to recompile after every change, manually move the binary, or maintain shell aliases can easily interrupt the iteration flow. Cristian Felipe Patiño Rojas offers a creative solution: use npm’s bin mechanism to register a Swift script with a shebang as a global command. This allows the Swift script to be invoked just like a regular CLI tool, while npm only creates a global symlink and does not participate in building the Swift code.
Considering that launching a script through
swifthas a nontrivial cold-start cost, often taking several seconds, this approach is better suited to the development stage of Swift CLI tools. For small utilities like these, developers do not necessarily need to turn them into full Swift Packages from the very beginning. Starting with the lightest possible approach, then compiling and distributing the tool once it stabilizes, is often a better fit for real-world workflows.
WatchConnectivity was failing 40% of the time. So I stopped using it.
While building real-time communication for an Apple Watch app, Tarek Sabry ran into a very frustrating problem: Apple’s official WatchConnectivity framework was unstable, with a connection success rate of only around 60%. Eventually, Tarek stopped treating WatchConnectivity as the only reliable channel and introduced a parallel network transport path instead: BLE for service discovery, HTTP for data transfer, SSE for the push channel, plus frame IDs, ack confirmation, deduplication, and retransmission. This successfully raised the connection success rate from 60% to 99%, with an almost immediate effect.
The more interesting by-product of this approach is that, because it is entirely based on standard network protocols, the Watch does not know whether the other end is an iPhone, an Android phone, or any device with an IP address. According to the author, as of April 2026, this is the only publicly available Android ↔ Apple Watch communication solution. The solution has been open-sourced under the name WatchLink, supports Swift 6, and has no third-party dependencies.
Using SwiftUI to Build a Mac-assed App in 2026
The term “Mac-assed app” describes software that truly belongs on the Mac: it uses native system controls, deeply integrates with system features, and gets every interaction detail aligned with platform conventions. While building a macOS app entirely in SwiftUI, Paulo Andrade found that although SwiftUI provides enough APIs to cover simple scenarios, the framework itself often becomes an obstacle when developers try to reproduce the interaction conventions that Mac apps have accumulated over decades.
The article breaks down several clear shortcomings of SwiftUI on macOS today: custom lists struggle to fully reproduce the subtle differences between selection, loss of focus, and right-click context targets; there is no way to detect when a context menu is open, making it difficult to correctly highlight the object the menu applies to; visibility into drag state is almost nonexistent; keyboard navigation is easily “swallowed” once a TextField has focus; and toolbar layout control is far less precise than in AppKit. Paulo argues that SwiftUI still has not completed the handoff from AppKit.
Scheduling and handling background app refresh in SwiftUI
Background refresh in iOS may look simple at the API level, but it is easy to run into pitfalls in practice. In this article, Natalia Panferova demonstrates how to configure BGAppRefreshTaskRequest with the Background Tasks framework under the SwiftUI App lifecycle, and how to register the handling logic through the backgroundTask(_:action:) scene modifier. The article walks through the full basic flow of adding background refresh to a SwiftUI app, from enabling Background Modes and registering the task identifier in Info.plist, to submitting the background refresh request and handling the app when the system wakes it. The author also explains how to simulate a background task trigger on a physical device using an Xcode debugger command, making it easier to verify the logic during development.
It is worth noting that background refresh is not a precise scheduler. Even if
earliestBeginDateis set, the system decides whether and when to wake the app based on factors such as battery level and user behavior. Therefore, critical logic should not depend on it running on time.
How to avoid Swift 6 concurrency crashes
Even with zero compile-time warnings under Swift 6 strict concurrency mode, runtime safety is not guaranteed. The compiler injects dynamic isolation assertions at actor and GCD boundaries, and if the execution path does not match the compiler’s assumptions, the app can still crash in production with _dispatch_assert_queue_fail or _swift_task_checkIsolatedSwift.
Khoa lists several common triggering scenarios: closures defined inside an @MainActor context inherit main actor isolation, and if they are later called on a background thread by old-style callback or queue-based APIs, a crash may occur; in Combine pipelines, the position of receive(on:) affects closure isolation inheritance; and if a delegate method inherits main actor isolation because the entire class is marked @MainActor, but the SDK actually calls it from an internal queue, it can also trigger a runtime assertion.
The approaches Khoa introduces in this article can help developers quickly locate and fix common runtime crash points when migrating old projects to Swift 6. However, if time and energy allow, the more ideal direction is still to reorganize feature code according to Swift 6’s modern concurrency model, make isolation boundaries explicit, and reduce reliance on local annotations and patch-style fixes.
Tools
SwiftMetalNumerics: A Swift-native GPU numerical computing library for Apple Silicon
When building real-time audio analysis or on-device signal modeling on Apple platforms, developers often get stuck in an awkward middle ground. High-level APIs such as SoundAnalysis and AVAudioEngine are convenient, but they often behave like black boxes that only provide processed results. Once you need to control STFT, frequency binning, matrix operations, or feed extracted features into custom neural network layers, you often have to bridge back and forth between Accelerate/vDSP and low-level Metal. CPU-side computation can be fast, but once the downstream computation enters the GPU pipeline, data movement and synchronization may become the bottleneck for real-time processing.
Bugra Acemoglu’s SwiftMetalNumerics is a new project aimed precisely at this middle ground. It tries to wrap Metal / MPS / MPSGraph and Accelerate / LAPACK on Apple Silicon behind Swift APIs, bringing matrix computation, FFT/STFT, convolution, and basic neural network layers into a unified numerical computing interface. What makes it interesting is not merely “GPU acceleration,” but its attempt to take advantage of the unified memory architecture to minimize back-and-forth copying between DSP, matrix computation, and lightweight ML pipelines.
SwiftMetalNumerics is not a full replacement for Accelerate. For small-scale, one-off computations, the CPU path is often still faster. But if your task is a longer real-time signal processing or on-device inference pipeline, SwiftMetalNumerics presents a direction worth watching: connecting low-level mathematical control with GPU computing power in a more Swift-native and Apple Silicon-native way.
SwiftUI Preview Runner: A SwiftUI preview engine for custom tools and AI workflows
SwiftUI Preview Runner is an inspiring experimental project developed by Aryan Rogye. It does not launch a simulator, nor does it replicate Xcode Preview’s XPC-based preview mechanism. Instead, it writes a piece of SwiftUI code into a temporary Swift Package, compiles it into a dynamic library, dynamically loads it with dlopen, and renders it inside a host macOS app using NSHostingView.
As a result, it feels more like a “SwiftUI Playground” that can be embedded into custom toolchains: you can connect an editor, AI generation, an MCP validator, or other automation workflows in front of it, allowing generated SwiftUI code to immediately enter a feedback loop of “can it compile, and can it render?”
Note that the project targets macOS SwiftUI rendering, not the iOS simulator; compilation introduces latency; and dynamically loading code also means the security boundary must be handled with great care. Even so, as an exploration of how AI-generated SwiftUI can receive real runtime feedback, it is worth paying attention to.
Thanks for reading Fatbobman’s Swift Weekly! This post is public so feel free to share it.
CocoaPods 正在退场,SwiftPM 才刚到第二章
谷歌近期宣布,从下一个 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 的故事,才刚刚翻到第二章。
如果您发现这份周报或我的博客对您有所帮助,可以考虑通过 Buy Me a Coffee 支持我的创作。
近期推荐
警惕 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 获得真实运行反馈”的探索样本,它值得关注。


