Fatbobman's Swift Weekly #138
Stability > New Features

Stability > New Features
Whether it is SwiftUI or SwiftData, these foundational frameworks, into which Apple poured so much hope, promised a bright future when they were first introduced. However, their actual trajectory seems to have diverged from the original blueprint. The deeper I dive into these frameworks, the more I marvel at their elegant architectural design—yet I also find myself speechless at their underwhelming implementation. Watching the halo around these designs gradually fade, I cannot help but feel a sense of wistfulness.
Rumor has it that in the upcoming operating systems to be released this year (including iOS 27 and macOS 27), Apple will adopt a refinement strategy similar to the Mac OS X Snow Leopard era. This means prioritizing system stability, performance optimization, clearing legacy code, and squashing bugs over introducing disruptive visual redesigns or a flurry of new low-level features. If this indeed turns out to be the case, it would be deeply reassuring. Looking back at Issue #006 of the weekly newsletter published more than two years ago, rumors also circulated back then that Apple would focus on fixing existing defects and improving performance. Yet, judging by the actual user experience over the past two years, that goal seems to have fallen short.
With WWDC 26 less than ten days away, rather than more flashy new features, what I look forward to most this year is Apple delivering a grounded, more stable experience for both developers and consumers.
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 ☕️
Original
Taming Row Height and Spacing Jumps in SwiftUI List with a Custom Layout
In SwiftUI, adding animation to view state changes often takes very little code, but List does not always produce the kind of smooth transition we expect. This is especially noticeable when the height of content inside a row changes dynamically—for example, when a subtitle goes from empty to non-empty, or when updated text changes its line count. In these cases, the system’s default layout process can easily cause the row height to jump abruptly, leading to visible flickering, clipping, or sudden spacing changes. Starting from this common issue, this article gradually breaks down why dynamic row-height animations fail in List, and builds a solution entirely based on native SwiftUI capabilities through custom Layout, Animatable, LayoutValueKey, and state decoupling.
This is not an article primarily aimed at providing a ready-to-use component. Rather than focusing only on the final code, I hope to use this problem to outline a way of investigating SwiftUI layout issues: first understand why the framework “does not behave as expected,” then reorganize state, measurement, and layout along the grain of its mechanisms, so that the animation happens at the correct level.
Recent Recommendations
Stateless Actors
Actors are usually understood as tools for protecting mutable state, so an actor with no stored properties—a seemingly stateless actor—naturally raises the question: does it have any reason to exist? This article explores that question through examples such as NetworkClient, custom global actors, custom executors, and file-system access. It shows that stateless actors are not necessarily unreasonable, but they may introduce extra costs such as serialization, protocol-adaptation difficulties, and type-system propagation. The key question is what problem they are actually solving.
The most important point in the article is the “first rule of actors” proposed by Matt Massicotte: just like with any synchronization primitive, before using an actor, you should be able to clearly explain why it is necessary. A stateless actor is not necessarily wrong, but it may indicate that we are using an actor to solve a problem that does not actually require one. Only when it clearly takes on responsibilities such as isolation, serialization, executor adaptation, or protection of external state does it become a reasonable design.
Building a Custom Data Store in SwiftData
Many developers tend to think of SwiftData as Apple’s high-level wrapper around SQLite, but that underestimates its potential as a framework for object-graph management and persistence coordination. Whether we are talking about SwiftData or Core Data, their more important capability is not merely reading and writing a database for us, but keeping model objects, queries, context management, and the underlying storage clearly separated. As long as the underlying storage can exchange data in the way the framework expects, the data source does not have to be SQLite.
Mohammad Azam demonstrates this by implementing a JSON-file-based custom data store. The article shows that what SwiftData exchanges with the underlying store is not the live object marked with @Model, but a converted snapshot. Once you understand this, the responsibility boundaries of DataStoreConfiguration, DataStore, DefaultSnapshot, PersistentIdentifier, and fetch / save become much clearer: SwiftData is responsible for model objects, observation, change tracking, and SwiftUI integration, while the custom store is only responsible for reading and writing snapshots.
In Weekly Issue #127, I recommended DataStoreKit, a more engineering-oriented custom DataStore project. It does not simply replace SwiftData’s backend with JSON or file storage; instead, it attempts to reimplement a SwiftData-aware SQLite storage layer based on custom DataStore, with extensive work around predicate translation, inheritance, caching, persistent history, and background prefetching. Developers who want to explore this direction further can use it as an advanced reference.
Task Names in Swift Concurrency
GCD has queue labels, but tasks in Swift Concurrency long lacked a similar diagnostic identifier. With SE-0469 implemented in Swift 6.2, APIs such as Task, Task.detached, and task group addTask now support a name parameter. Developers can assign short, readable names to concurrent tasks, making it easier to locate specific execution units in LLDB, Instruments, and logs.
Artem Novichkov walks through the usage and limitations of task names, and reminds developers that a task name should be treated as diagnostic information, not as part of program logic. It is useful for debugging, performance analysis, and log investigation, but it should not carry business state.
UniqueBox, Ref, and MutableRef in Swift 6.4
Swift 6.4 continues the language’s evolution around ownership, borrow, noncopyable types, lifetime dependency, Span, and MutableSpan. Through the three types UniqueBox, Ref, and MutableRef, Artem Mirzabekian introduces Swift’s new expressive capabilities around storage location, ownership, and access lifetime. Their significance is not that ordinary business code should immediately start using these low-level building blocks, but that Swift is gradually elevating relationships that previously depended on class boxes, UnsafePointer, or compiler-internal reasoning into language models that can be written into API shapes and checked by the type system.
Specifically, UniqueBox expresses that “a value lives on the heap and is owned by a single owner,” while Ref and MutableRef correspond to shared reading and exclusive mutation within a given lifetime. In other words, these types do not merely add a convenient API for a specific business scenario; they provide a more precise low-level vocabulary for describing “where a value lives, who owns it, and who can access it.”
Clarification on Current CloudKit CKAsset File Size Limits
There has long been a confusing claim in the community around the file-size limit of CKAsset: some documentation has mentioned a 50 MB limit, but that mostly applies to CloudKit Web Services rather than CKAsset uploads through the native CloudKit framework on Apple platforms. This time, an Apple Frameworks Engineer gave a very clear answer on the developer forums: a single CKAsset supports up to 50 GB, assuming the user has enough remaining iCloud storage. Apps therefore need to correctly handle CKError.quotaExceeded.
I am introducing this Q&A in the weekly not just because it clarifies a number, but because it draws an important boundary: the fact that
CKAssetsupports large files does not mean large-file syncing is a simple or automatically reliable engineering problem. The follow-up discussion also mentions that large file transfers often need to consider background execution when an app is suspended or terminated, and can use long-lived CloudKit operations or schedule uploads throughBGProcessingTaskinBackgroundTasks. In other words, 50 GB answers the question “does CloudKit allow it?”; background transfer, failure recovery, quota handling, and user experience still need to be designed carefully by the app.
Providing Useful Debugging Information for Agents
As AI agents become increasingly involved in compiling, testing, and diagnosing Swift projects, a very practical problem has become more prominent: the output of xcodebuild is simply too long. If the complete log is handed directly to an agent, it not only consumes a large number of tokens, but also risks burying the useful information in noise. The Xcsift tool introduced by Lee young-jun in this article is designed precisely for this scenario. Unlike xcpretty, which is mainly aimed at human readability, Xcsift organizes xcodebuild / SwiftPM output into structured results that are more suitable for LLM consumption, including compile errors, warnings, test failures, coverage, and build timing.
One easily overlooked detail in AI-assisted development workflows is that we should not let agents “read everything and decide what matters by themselves.” Instead, we should try to perform filtering and structuring at the tool layer first. Xcsift is a practical solution built on top of existing
xcodebuildoutput, while the Tuist team previously discussed structured build data beyond build logs in Teaching AI to Read Xcode Builds. Viewed together, they show two levels of build diagnosis in agentic coding: first reduce noise, then improve semantics.
The MiniSwift Story
I previously recommended MiniSwift in the weekly. At that time, the most fascinating part was that Ugur Toprakdeviren had implemented a Swift compiler frontend and WASM backend from scratch in C, without relying on LLVM, Clang, or Apple’s official toolchain. In this article, the author fills in the story behind the project: it did not begin as a “show-off compiler project,” but from a very specific question—could one get a more stable SwiftUI preview on canvas, free from the instability of WebView or Xcode Preview?
The reason MiniSwift is worth recommending again is that it has clearly taken a big step from an early compiler prototype toward its original goal. It no longer merely compiles Swift code to WASM; through a custom UIIR, canvas renderer, and diff engine, it can now render SwiftUI code directly into an interactive preview in the browser. Judging from the official demo, @State, basic layout, buttons, text, and some modifiers can already provide immediate feedback in the browser similar to SwiftUI Preview. In other words, the previously mentioned idea of a “SwiftUI browser preview that does not crash” is turning from a concept into a tool that can be experienced directly.
Tools
MistKit: Letting Server-Side Swift Access CloudKit
Developed by Leo G Dion, MistKit has a clear goal: to wrap Apple’s CloudKit Web Services REST API in a modern Swift interface, allowing server-side Swift, command-line tools, and environments such as Linux and Windows—where the native CloudKit framework is unavailable—to access the same CloudKit containers. It is not meant to replace the CloudKit framework on Apple platforms, but to fill the gap in scenarios where the native framework cannot be used directly.
The project builds its lower-level client on top of swift-openapi-generator, while providing higher-level capabilities such as async/await, type-safe CloudKit operations, structured errors, record querying and CRUD, record / zone changes, asset upload, and user-identity-related functionality. For authentication, it supports API Token, Web Auth Token, and Server-to-Server. Server-to-Server is mainly for the public database, while operations involving user context in private or shared databases require Web Auth Token. This makes it suitable for background tasks, CLIs, content catalogs, public database management, and data bridging between the web and Apple devices.
CloudKit is often seen as a “client-side service,” but MistKit makes it more natural for servers to participate in the CloudKit ecosystem. For example, a scheduled job could maintain a software version catalog, RSS aggregation content, or app asset packages in the public database; or, after user authorization, a backend could process data in a user’s private database. For apps already relying on CloudKit, it provides a path for extending data-processing capabilities beyond Apple platforms.
Thanks for reading Fatbobman’s Swift Weekly! This post is public so feel free to share it.
稳定 > 新功能
无论是 SwiftUI 还是 SwiftData,这些苹果寄予厚望的基础框架,在推出时都描绘了充满光明的未来,但实际走势似乎都和最初的设定不太一样。当我越深入了解这些框架,就越对它们精妙的架构设计所折服,同时也对那些不尽如人意的实现感到无语。眼看着这些设计的光环逐渐褪去,心中不免唏嘘。
传闻苹果在今年即将发布的全新操作系统(包括 iOS 27 和 macOS 27)中,将采取类似于当年 Mac OS X Snow Leopard 时代的调整策略——将重心放在系统稳定性、性能优化、清理老旧代码和修复 Bug 上,而不是引入颠覆性的视觉设计或繁多的底层新功能。如果真能如此,那实在是令人欣慰。回看两年多前的 第六期周报,当时也传出过苹果要集中修复彼时存在的缺陷并提高软件性能,但至少从过去两年的实际使用体验来看,这个目标似乎未能达成。
距离 WWDC 26 已经不足十天了。相较于更多、更炫的新功能,我今年更期待苹果能给开发者和消费者带来一次脚踏实地、更加稳定的体验。
如果您发现这份周报或我的博客对您有所帮助,可以考虑通过 Buy Me a Coffee 支持我的创作。
原创
用自定义 Layout 化解 SwiftUI List 的行高与间距跳变
在 SwiftUI 中,为视图状态变化添加动画往往只需要很少的代码,但 List 并不总能给出符合预期的过渡效果。尤其当 row 内部的内容高度发生变化时,例如副标题从无到有、文本行数因数据更新而改变,系统默认的布局过程很容易让行高直接硬切,进而带来闪烁、裁剪或 spacing 突变。本文从这一常见问题出发,逐步拆解 List 动态行高动画失效的原因,并通过自定义 Layout、Animatable、LayoutValueKey 与状态解耦,构建出一套完全基于 SwiftUI 原生能力的解决方案。
这并不是一篇以提供开箱即用组件为主要目标的文章。相比最终代码,我更希望借这个问题梳理一条 SwiftUI 布局问题的探查路径:先理解框架为什么“不按预期工作”,再顺着它的机制重新组织状态、测量与布局,让动画真正发生在正确的层级上。
近期推荐
无状态 actor (Stateless Actors)
Actor 通常被理解为“用来保护可变状态”的工具,因此一个没有存储属性、看似无状态的 actor 很容易让人疑惑:它到底有没有存在的必要?文章以 NetworkClient、自定义 global actor、custom executor 与文件系统访问等场景为例,说明无状态 actor 并非一定不合理,但也可能带来串行化、协议适配和类型系统传播等额外成本;关键在于它究竟是在解决什么问题。
这篇文章最值得关注的点,是 Matt Massicotte 提出的“actor 第一原则”:像使用任何同步原语一样,在使用 actor 之前,应该能清楚说明它为什么必要。无状态 actor 并不一定是错误,但它很可能意味着我们正在用 actor 解决一个并不需要 actor 的问题;只有当它确实承担了隔离、串行化、executor 适配或外部状态保护等明确职责时,才是合理的设计。
为 SwiftData 构建自定义存储格式 (Building a Custom Data Store in SwiftData)
很多开发者容易将 SwiftData 理解为苹果官方对 SQLite 的高层封装,但这其实低估了它作为对象图管理与持久化协调框架的潜力。无论是 SwiftData 还是 Core Data,更重要的能力都不只是替我们读写数据库,而是让模型对象、查询、上下文管理与底层存储之间保持清晰分工。只要底层存储能够按照框架要求完成数据交换,数据来源就不一定非得是 SQLite。
Mohammad Azam 通过实现一个基于 JSON 文件的 custom data store,展示了 SwiftData 与底层存储之间真正交换的并不是 @Model 标记的实时对象,而是经过转换后的 snapshot。理解这一点之后,DataStoreConfiguration、DataStore、DefaultSnapshot、PersistentIdentifier 以及 fetch / save 的职责边界都会变得清晰起来:SwiftData 负责模型对象、观察、变更追踪和 SwiftUI 集成,而自定义 store 只负责读取和写入快照。
在 #127 期周报中,我曾推荐过一个更工程化的自定义 DataStore 项目 DataStoreKit。它并不是简单地把 SwiftData 后端换成 JSON 或文件存储,而是尝试基于 custom DataStore 重新实现一套 SwiftData-aware 的 SQLite 存储层,并在 predicate 翻译、继承、缓存、历史追踪以及后台预取等方面做了大量扩展。想在此方向进一步研究的开发者,可以将它作为进阶参考。
给 Swift 并发任务命名 (Task Names in Swift Concurrency)
GCD 有 queue label,Swift Concurrency 的 task 却长期缺少类似的诊断标识。随着 SE-0469 在 Swift 6.2 中实现,Task、Task.detached、task group 的 addTask 等 API 开始支持 name 参数,开发者可以为并发任务添加简短、可读的名称,从而在 LLDB、Instruments 和日志中更容易定位具体执行单元。
Artem Novichkov 梳理了 task name 的用法与限制,并提醒开发者:task name 应只作为诊断信息,而不是程序逻辑的一部分。它适合辅助调试、性能分析和日志排查,但不应承载业务状态。
UniqueBox, Ref, and MutableRef in Swift 6.4
Swift 6.4 延续了近几年围绕 ownership、borrow、noncopyable type、lifetime dependency、Span 与 MutableSpan 等方向的演进。Artem Mirzabekian 通过 UniqueBox、Ref 和 MutableRef 这三个类型,介绍了 Swift 在存储位置、所有权与访问生命周期上的新表达能力。它们的意义并不是鼓励普通业务代码立刻改用这些底层构件,而是展示 Swift 正在把过去依赖 class box、UnsafePointer 或编译器内部推理的关系,逐步提升为可以写进 API 形状、并由类型系统检查的语言模型。
其中,UniqueBox 用来表达“一个值位于堆上,并由单一所有者持有”;Ref 和 MutableRef 则分别对应某段生命周期内的共享读取与独占修改。换句话说,这些类型补上的不是某个具体业务场景的便利 API,而是一套更精确描述“值在哪里、归谁所有、谁能访问”的底层词汇。
关于 CloudKit CKAsset 当前文件大小限制的澄清 (Clarification on Current CloudKit CKAsset File Size Limits)
关于 CKAsset 的文件大小上限,社区里长期存在一个容易混淆的说法:有些文档中提到过 50 MB 限制,但那更多对应 CloudKit Web Services,而不是 Apple 平台上通过原生 CloudKit framework 上传的 CKAsset。这次 Apple Frameworks Engineer 在开发者论坛中给出了一个非常明确的回答:单个 CKAsset 最高支持 50 GB,前提是用户仍有足够的 iCloud 存储空间,因此应用需要正确处理 CKError.quotaExceeded。
之所以在周报中介绍这个问答,并不只是因为它澄清了一个数字,而是因为它划清了一个重要边界:
CKAsset支持大文件,并不等于大文件同步就是简单可靠的工程问题。后续讨论也提到,大文件传输往往需要考虑应用挂起、终止后的后台执行能力,可以结合 long-lived CloudKit operations,或通过BackgroundTasks中的BGProcessingTask来安排上传任务。也就是说,50 GB 回答的是“CloudKit 是否允许”,而后台传输、失败恢复、配额处理和用户体验,仍然需要应用自己认真设计。
为 Agent 提供有效的调试信息 (Don’t allow the agent reading whole output of Xcodebuild)
随着 AI agent 越来越多地参与 Swift 项目的编译、测试和问题定位,一个很现实的问题开始变得突出:xcodebuild 的输出实在太长了。如果直接把完整日志交给 agent,不仅会消耗大量 token,也容易让真正有用的信息被淹没在噪声中。Lee young-jun 在本文中介绍的 Xcsift 正是为这个场景设计的工具:它不像 xcpretty 那样主要面向人类阅读,而是把 xcodebuild / SwiftPM 的输出整理成更适合 LLM 消费的结构化结果,例如编译错误、警告、测试失败、覆盖率和构建耗时等。
AI 辅助开发工作流中一个容易被忽略的细节是:我们不应该让 agent “读完所有内容再自己判断重点”,而应该尽量在工具层先完成信息筛选与结构化。Xcsift 是一个基于现有
xcodebuild输出的实用方案,而 Tuist 团队此前在 Teaching AI to Read Xcode Builds 中则进一步讨论了构建日志之外的结构化 build data。两者放在一起看,正好体现了 agentic coding 在构建诊断上的两个层次:先减少噪声,再提升语义。
The MiniSwift Story
我在之前的周报中曾推荐过 MiniSwift,当时最吸引人的部分,是 Ugur Toprakdeviren 在不依赖 LLVM、Clang 或 Apple 官方工具链的情况下,用 C 从零实现了 Swift 编译器前端与 WASM 后端。而在这篇文章中,作者补全了项目背后的来龙去脉:它最初并不是为了“炫技式地写一个编译器”,而是源于一个很具体的问题——能不能摆脱 WebView 或 Xcode Preview 的不稳定,用 canvas 得到更稳定的 SwiftUI 预览。
这次值得再次推荐,是因为 MiniSwift 已经明显从早期 compiler prototype 向它最初的目标迈进了一大步。现在它不只是把 Swift 代码编译到 WASM,而是进一步通过自定义 UIIR、canvas renderer 和 diff engine,把 SwiftUI 代码直接渲染成浏览器中的可交互预览;从官网展示来看,@State、基础布局、按钮、文本和部分修饰器已经可以在网页中形成类似 SwiftUI Preview 的即时反馈。也就是说,之前提到的“不会崩溃的 SwiftUI 浏览器预览”,正在从设想变成一个可以直接体验的工具。
工具
MistKit:让服务端 Swift 访问 CloudKit
由 Leo G Dion 开发的 MistKit 目标很明确:把 Apple 的 CloudKit Web Services REST API 封装成现代 Swift 接口,让服务端 Swift、命令行工具,以及 Linux、Windows 等没有原生 CloudKit framework 的环境,也能访问同一套 CloudKit 容器。它并不是要替代 Apple 平台上的 CloudKit framework,而是补足那些无法直接使用原生框架的场景。
项目基于 swift-openapi-generator 构建底层客户端,上层提供 async/await、类型安全的 CloudKit 操作、结构化错误、record 查询与增删改、record / zone changes、asset upload 和用户身份相关能力。认证方面覆盖 API Token、Web Auth Token 与 Server-to-Server;其中 Server-to-Server 主要用于 public database,涉及 private 或 shared database 的用户上下文操作则需要 Web Auth Token。这使它适合后台任务、CLI、内容目录、公共数据库管理,以及 Web 与 Apple 设备之间的数据桥接。
CloudKit 常被视为“客户端服务”,但 MistKit 让服务端参与 CloudKit 生态变得更自然。例如,用定时任务维护 public database 中的软件版本目录、RSS 聚合内容、应用素材包;或在用户授权后,让后端处理 private database 中的数据。对已经依赖 CloudKit 的应用来说,它提供了一条在 Apple 平台之外扩展数据处理能力的路径。

