Fatbobman's Swift Weekly #133
Swift Concurrency is Gaining Broader Adoption

Swift Concurrency is Gaining Broader Adoption
It has been almost 5 years since Swift 5.5 introduced a new concurrency model aligned with modern programming paradigms. From 5.5 to the current 6.3, the Swift community has been actively advancing the evolution of concurrency APIs through small, iterative steps. However, this process hasn’t exactly been smooth sailing for developers, who had to grapple with a plethora of new keywords, complex isolation concepts, and some confusing “anti-patterns.”
As a newsletter editor, I’ve seen a massive amount of complaints and pleas for help over the past few years, but this sense of anxiety seems to have noticeably faded since the beginning of this year. In recent months, there have been more and more success stories shared within the community about migrating legacy projects to the new concurrency model. Although there are still a few scattered complaints, the prevailing attitude has shifted toward active embrace. Looking at the practical results, the new code—born from the growing pains of refactoring—has significantly reduced the cognitive load on developers when maintaining concurrency states.
Of course, behind this phenomenon, aside from developers becoming increasingly proficient with the new system, the maturity of the ecosystem also deserves a lot of credit. Over the past few years, both first-party and third-party frameworks and components have gradually completed their transition to the new concurrency implementations. This has greatly lowered the barrier to entry, allowing an increasing number of developers to immediately enjoy the convenience and data safety it brings.
There is also an interesting factor at play: thanks to the selfless sharing of many excellent content creators and open-source authors in the community, AI has accumulated a much richer corpus of Swift concurrency data over the past year or two. The new generation of large language models released in recent months clearly possesses a more accurate understanding of Swift’s concurrency rules, which has greatly accelerated developers’ mastery and application of the new concurrency.
Imperceptibly, the development of Swift’s new concurrency has entered a virtuous cycle. As can be seen from the new features in Swift 6.3, following the release of version 6.2, the evolution of Swift concurrency has largely entered a stable phase. Consequently, the community’s focus has shifted toward broader areas such as cross-platform and embedded development.
Perhaps the term “new concurrency model” in Swift can soon drop the “new” label and officially become part of the lifeblood of our daily development. Of course, it’s not entirely impossible that in another five to ten years, yet another “brand-new” concurrency model will arrive to amaze (or torture) us once again.
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
The Flaky Test That Taught Me How Swift Concurrency Actually Works
Writing a test MockClock to support debounce behavior may not seem difficult for many Swift developers. But if you overlook differences in execution context — the executor — the result may be less deterministic than expected. Through the debugging process of a real-world case, Xiangyu reveals many details of modern Swift concurrency: nonisolated async functions hop away from the caller’s actor; Task.yield() only yields the current executor; and isolated parameters plus #isolation can let utility functions “run with the caller.” The value of this article is not in how to implement a test Clock, but in how clearly it shows the real relationship between tasks, actors, executors, and isolation in Swift Concurrency.
As Swift evolves, the behavior of
nonisolatedis no longer always equivalent to “necessarily hopping away from the current caller.” It can also be affected by the language mode and settings such asNonisolatedNonsendingByDefault. I ran into a similar issue, and solved it in the same way: withisolated+#isolation.
What’s that “structured” in Structured Concurrency?
Although Task has a handle and can be cancelled, making it look “more structured” than dispatch_async, its definition in Swift’s modern concurrency system is actually quite clear: Task is an unstructured top-level task. In this article, Max Seelemann explains what “structured” really means: the truly structured concurrency constructs are essentially async let and TaskGroup. What they share is an inescapable dependency relationship: the caller must wait for the subtasks to complete, and cancellation automatically propagates downward. Task, by contrast, is the opposite — it can be freely created and forgotten, with a lifecycle detached from the caller.
The article ends with a practical suggestion: use closures instead of Task whenever possible. Expressing a unit of work as () async -> Void is simpler, easier to test, and naturally inherits cancellation behavior from the calling context compared with passing around a Task object.
SwiftUI: Refreshable Task Cancellation
.refreshable accepts an async closure in which developers can fetch new data and update view state. But it has an easy-to-miss pitfall: if you modify the @State that drives the current view while the closure is running, the resulting redraw may cancel the refreshable task before it completes. In this article, Anton Gubarenko clearly explains this behavior: the lifecycle of .refreshable is tied to the view, and if a redraw is triggered during the refresh process, SwiftUI may terminate the task early — in other words, the task is cancelled by the very update it caused.
The author gives two possible fixes: either collect intermediate results in a local variable and update state once at the end, avoiding repeated redraws; or wrap the actual work in Task { }.value, allowing it to “detach” from the current refresh task and avoid being interrupted by the view lifecycle.
The issue faced by
.refreshablealso exists with.task. This is not a bug, but a concrete expression of SwiftUI’s structured concurrency model: async tasks are attached to the view lifecycle and are cancelled when that lifecycle ends. Without realizing this, it is easy to create a “self-cancellation” scenario between state updates and task execution.
Swift 6.3 experimentalCGen guide: SwiftPM natively supports C code generation
The experimentalCGen feature introduced in Swift 6.3 fills a long-standing gap in SwiftPM: C-family artifacts generated by Build Tool Plugins can finally participate directly in the compilation of C module targets, without relying on external scripts or precommitted generated files. This means the complete build graph can be closed within SwiftPM — local builds, CI, and Xcode can all use the same rules instead of each maintaining part of the build knowledge. In this article, Snow gives a detailed overview of the background, usage, and migration path for this feature.
experimentalCGenstill has clear limitations. For example, each target can only have one generated module map, and headers referenced by that module map must be located in the same directory. As a result, it is better suited to code generation workflows with clear boundaries and stable outputs, rather than serving as a replacement for a full native build system.
How I migrated 300 screens to SwiftUI and what I learned
Migrating 300 screens to SwiftUI sounds like a complete transformation, but the strategy adopted by Artem Mirzabekian and his team was quite restrained: SwiftUI was responsible only for UI construction, while navigation remained in UIKit. This separation was not a compromise, but a deliberate choice — allowing the team to benefit from SwiftUI’s strengths in layout and component reuse while avoiding unnecessary risks around navigation, deep linking, and complex flows. The article records recurring issues during the migration: the team was used to imperative thinking and often wrote SwiftUI as “UIKit with different syntax”; side effects were placed inside body; onAppear was treated as viewDidLoad; nested ObservableObject structures led to unexpected state propagation problems. After continuous review and internal workshops, the team eventually settled on a clear architectural direction: MVVM + enum-driven state and interaction modeling + Use Cases for separating business logic.
Even outside migration projects, combining SwiftUI and UIKit is a realistic and effective choice. The two frameworks are not opposed to each other. Mixing them is not the hard part; the real challenge is writing code that fits each framework’s intended mental model.
Tools
Mini Swift: A Swift Compiler Written in Pure C
A fascinating project with a strong hacker spirit. Without using LLVM, Clang, or any official Apple toolchain, Ugur Toprakdeviren single-handedly wrote a lightweight Swift compiler in more than 70,000 lines of C. Starting from lexical analysis and the AST, he built the pipeline all the way to a backend that directly outputs WASM, bringing an astonishing sub-0.1 ms “instant compilation” experience to the web. In an era where pulling in hundreds of megabytes of dependencies is common, this zero-dependency act of “building the wheel by hand” feels almost romantically retro.
The project began with a simple goal: to build a SwiftUI browser preview that would not crash. The author also revealed that he is currently developing a layer called UIIR — a UI intermediate representation — with the eventual goal of mapping SwiftUI code into a platform-independent set of instructions that can render native-like results directly on the Web or even Android, fulfilling his original vision.
The compiler frontend’s core code — lexer, parser, and semantic analysis — is currently open source.
If combined with a terminal-based WASM runtime (such as Wasmtime), Mini Swift could significantly alleviate the current pain point of slow cold starts when writing terminal scripts in Swift, truly enhancing the Swift scripting experience. I am very much looking forward to seeing community explorations in this direction.
Yotei: A SwiftUI Calendar Component Library
Yotei is an iOS calendar component library by Mikalai Zmachynski. The project provides SwiftUI APIs while relying on UIKit to maintain scrolling performance in high-load scenarios such as schedule lists, pagination, and timelines. It includes components such as a date picker, day timeline, month grid, and schedule list, along with fairly detailed customization entry points.
Yotei demonstrates an increasingly common trade-off in complex SwiftUI components: SwiftUI handles composition, state binding, and extension points, while UIKit provides more mature and stable list and paging behavior.
Runtime Viewer
If you still remember RuntimeBrowser, then Runtime Viewer, developed by Mx-Iris, can be seen as a modern rewrite of that idea for today’s Apple platform context. It can browse not only Objective-C runtime information, but also Swift types, enum layouts, VTable offsets, and more, while offering a code-reading experience close to Xcode.
It is not merely a tool that “shows more interfaces.” Instead, it brings runtime inspection, cross-process communication, code injection, Bonjour device discovery, and even MCP integration into a single tool. For developers interested in reverse engineering, security research, learning system frameworks, or simply understanding the internal structure of a binary, it feels more like a runtime inspection workbench for modern Apple platforms.
Thanks for reading Fatbobman’s Swift Weekly! This post is public so feel free to share it.
Swift 并发正被更广泛地接纳
从 Swift 5.5 引入符合现代编程思想的新并发模型算起,一转眼快 5 年了。从 5.5 到目前的 6.3,Swift 社区一直在采用小步迭代的方式,积极推进并发 API 的演进。但在应对过多的新关键字、复杂的隔离概念以及一些容易引发困扰的“反模式”时,这个过程对开发者来说并不算顺利。
作为一个周报编辑,我在过去几年间看到过大量的吐槽与求助,但这种焦虑感似乎从今年初开始明显转弱了。最近几个月,社区中出现了越来越多将旧项目迁移到新并发模型的成功分享。尽管其中仍夹杂着少量的抱怨,但主流态度已经展现出积极拥抱的态势。从实际的应用效果来看,经历过阵痛重构后的新代码,极大地降低了开发者在维护并发状态时的心智负担。
当然,这一现象的背后,除了开发者自身对新体系的掌握越来越熟练外,生态的成熟也功不可没。在过去的几年里,无论是第一方还是第三方框架与组件,都逐渐完成了向新并发实现的过渡。这大大降低了用户的使用门槛,让越来越多的开发者能在第一时间享受到新并发带来的便捷与数据安全。
此外还有一个有趣的因素:得益于社区众多优秀内容创作者、开源作者的无私分享,AI 在过去一两年中积累了更丰富的 Swift 并发语料。过去几个月间推出的新一代大语言模型,明显对 Swift 的并发规则有了更准确的理解,这也大大加快了开发者对新并发的掌握和应用。
在不知不觉中,Swift 新并发的发展已经进入了一个良性循环。从 Swift 6.3 的新功能就能看出,在经历了 6.2 版本之后,Swift 的并发演进已基本进入稳定期,社区的关注焦点也随之转移到了跨平台、嵌入式等更广阔的领域。
或许,Swift 的“新并发模型”这个称呼,很快就可以摘掉“新”的帽子,正式融入日常开发的血液中了。当然,也不排除再过个五到十年,又会有一个“全新”的并发模型再次惊艳(或折磨)我们。
如果您发现这份周报或我的博客对您有所帮助,可以考虑通过 爱发电,Buy Me a Coffee 支持我的创作。
近期推荐
一个偶发性测试失败,揭开了 Swift 并发调度的真相 (The Flaky Test That Taught Me How Swift Concurrency Actually Works)
为支持 debounce 行为编写一个测试用的 MockClock,对很多 Swift 开发者来说似乎并不复杂。但如果忽略了执行上下文(executor)的差异,结果可能就没有想象中那么确定。Xiangyu 通过一个实际案例的调试过程,揭示了 Swift 现代并发中的诸多细节:nonisolated async 函数会跳离调用者 actor;Task.yield() 只让出当前执行器;isolated 参数 + #isolation 可以让工具函数“随调用者运行”。本文的价值不在于如何实现一个测试用的 Clock,而在于它清晰地展示了 Swift Concurrency 中「任务、actor、executor、隔离」之间的真实关系。
随着 Swift 版本的发展,
nonisolated的行为也不再总是等同于“必然跳离当前调用者”,而会受到语言模式以及NonisolatedNonsendingByDefault等设置的影响。我也碰到过和本文类似的问题,同样也是通过isolated+#isolation来解决的。
「结构化并发」里的「结构化」究竟是什么意思? (What’s that “structured” in Structured Concurrency?)
尽管 Task 有句柄、可取消,比 dispatch_async 看起来“更有结构”,但在 Swift 的现代并发系统中,它的定义其实很明确:Task 是 unstructured top-level task。Max Seelemann 在本文中解释了“结构化”的真正含义:真正属于结构化并发的,其实只有两种——async let 和 TaskGroup。它们的共同点在于一种不可逃避的依赖关系:调用者必须等待子任务完成,且取消会自动向下传播。而 Task 则完全相反——可以被随意创建和遗忘,其生命周期与调用者脱钩。
文章最后给出了一条颇具实践意义的建议:能用闭包就尽量不用 Task。将工作单元表达为 () async -> Void,相比传递一个 Task 对象,更简单、更易测试,同时取消行为也会自然继承自调用上下文。
.refreshable 自己终结自己的陷阱 (SwiftUI: Refreshable Task Cancellation)
.refreshable 接受一个异步闭包,开发者可以在其中获取新数据并更新视图状态。但它有一个容易踩到的陷阱:如果在闭包执行过程中修改了驱动当前视图的 @State,由此触发的视图重绘,可能会让 refreshable 的任务在完成前被取消。Anton Gubarenko 在本文中对这一行为做了清晰解释:.refreshable 的任务生命周期与视图绑定,当刷新过程中触发重绘时,SwiftUI 可能提前终止该任务,也就是“任务被自己触发的更新所取消”。
作者给出了两种修复思路:要么将中间结果收集到本地变量,最后一次性更新状态,避免多次触发重绘;要么将实际工作包裹在 Task { }.value 中,使其从当前刷新任务中“脱离”,从而避免被视图生命周期打断。
.refreshable面临的问题,在.task中同样存在。这并不是 Bug,而是 SwiftUI 对结构化并发的一种具体体现:异步任务依附于视图生命周期,并随其结束而被取消。如果没有意识到这一点,就很容易在状态更新与任务执行之间制造出“自我取消”的场景。
Swift 6.3 experimentalCGen 指南:SwiftPM 原生支持 C 代码生成
Swift 6.3 引入的 experimentalCGen 填补了 SwiftPM 长期以来的一个关键缺口:Build Tool Plugin 生成的 C-family 产物,终于可以直接参与 C module target 的编译,而不再依赖外部脚本或预提交的生成文件。这意味着完整的构建图可以在 SwiftPM 内闭环——本地、CI、Xcode 使用同一套规则,不再各自维护一部分构建知识。Snow 在本文中详细梳理了其背景、用法与迁移思路。
experimentalCGen目前仍有明确限制,例如同一 target 只能有一个生成的 module map,且 module map 引用的 header 需要位于同一目录。因此它更适合边界清晰、输出稳定的代码生成场景,而不是用来替代完整的原生构建系统。
将 300 个视图迁移到 SwiftUI 后所学到的 (How I migrated 300 screens to SwiftUI and what I learned)
将 300 个界面迁移到 SwiftUI,听起来像一次彻底的转型,但 Artem Mirzabekian 及其团队的策略却相当克制:SwiftUI 只负责 UI 构建,导航依然留在 UIKit。这种分层并非妥协,而是有意为之——让团队在享受 SwiftUI 布局与组件复用优势的同时,避免在导航、深链路和复杂流程上引入不必要的风险。文章记录了迁移中反复出现的问题:团队习惯了命令式思维,常把 SwiftUI 当作“语法不同的 UIKit”来写;body 中夹带副作用、将 onAppear 当作 viewDidLoad 使用、嵌套 ObservableObject 导致状态传播不如预期……这些问题在迁移过程中一再出现。经过持续的审查与内部工作坊,团队最终沉淀出一套清晰的架构方向:MVVM + 枚举驱动的状态与交互建模 + Use Case 分离业务逻辑。
即便不是迁移项目,SwiftUI 与 UIKit 混用也是一种现实且有效的选择。两个框架并非对立,混用本身不难,难的是在各自的思维模型下写出符合其设计意图的代码。
工具
Mini Swift: 纯 C 写的 Swift 编译器
一个很有趣且极具极客精神的项目。Ugur Toprakdeviren 在不使用 LLVM、Clang 或苹果任何官方工具链的情况下,单枪匹马用 7 万多行 C 语言“手搓”了一个轻量级的 Swift 编译器。他从词法分析、抽象语法树一路写到了直接输出 WASM 的后端,为网页端带来了 0.1 毫秒级“秒编译”的惊人体验。在这个动辄拉入几百兆依赖包的时代,这种零依赖的“徒手造轮子”行为无疑是一种复古的浪漫。
这个项目源于作者一个简单初衷:做一个不会崩溃的 SwiftUI 浏览器预览。作者透露,他目前正在开发一个名为 UIIR(UI 中间表示)的层,最终目的是想把 SwiftUI 代码映射成一套平台无关的指令,直接在 Web 甚至 Android 上渲染出原生效果,以此来完成他最初的设想。
该编译器前端的核心代码(词法、语法、语义分析)目前已 开源。
如果结合终端版本的 WASM 运行时(如 Wasmtime 等),或许 Mini Swift 可以大幅改善目前 Swift 编写终端脚本时冷启动过慢的痛点,真正提升 Swift 的脚本化体验。我很期待社区在这个方向上的尝试。
Yotei: SwiftUI 日历组件库
Yotei 是一个 iOS 日历组件库,作者是 Mikalai Zmachynski。项目提供了 SwiftUI API,同时在日程列表、分页和时间轴等高负载场景中借助 UIKit 保持滚动性能。它有 date picker、day timeline、month grid、schedule list 等组件,也给了较细的定制入口。
Yotei 展示了一种在复杂 SwiftUI 组件中越来越常见的取舍:SwiftUI 负责组合、状态绑定与扩展点,UIKit 负责更成熟、稳定的列表和分页体验。
Runtime Viewer
如果你还记得 RuntimeBrowser,那么 Mx-Iris 开发的 Runtime Viewer 可以看作它在今天苹果平台语境下的一次现代化重写。它不仅能浏览 Objective-C runtime 信息,也把 Swift 类型、枚举布局、VTable 偏移等内容纳入了分析范围,并提供接近 Xcode 的代码阅读体验。
它不只是“能看更多接口”,而是把运行时检查、跨进程通信、代码注入、Bonjour 设备发现,甚至 MCP 接入放进了同一个工具里。对逆向、安全研究、系统 Framework 学习,或者只是想理解某个二进制内部结构的开发者来说,它都更像一个面向现代 Apple 平台的 runtime inspection 工作台。

