当每一次写入都有了新价格 -- 肘子的 Swift 周报 #144

当每一次写入都有了新价格 -- 肘子的 Swift 周报 #144
当每一次写入都有了新价格最近OpenAI 的 Codex 被发现存在一个日志写入问题程序会持续向本地数据库写入大量日志导致文件不断膨胀并产生远超正常水平的磁盘写入。类似的 Bug 过去并不少见。放在几年前人们大概只是删除文件、重启应用然后等待下一个版本修复。但这一次大家的反应明显不同。比起“还剩多少磁盘空间”更多人开始关心 SSD 已经累计写入了多少数据会不会影响闪存寿命以及未来更换设备时需要为同样容量的内存和存储多付多少钱。Codex 的 Bug 未必比过去那些失控的日志程序更加严重。真正发生变化的是每一次无意义的写入似乎都重新有了价格。随着 AI 算力需求迅速增长存储器厂商越来越倾向于优先保障 HBM、服务器内存和企业级 SSD 等利润更高、需求更旺盛的产品。消费级内存和 SSD 能够分配到的产能因此受到挤压价格也不断走高。内存和闪存本来就是典型的周期性行业价格上涨和下跌并不罕见。但这一轮变化令人不安的地方在于它似乎不再只是一次简单的库存周期而是伴随着产业资源向 AI 基础设施长期倾斜。即使价格最终仍会回落消费者恐怕也很难迅速回到过去那种“大容量近乎免费”的心理预期。当存储不再显得取之不尽时开发者或许也应该转变对资源时的态度。在我最开始接触电脑时Apple II 常见的内存配置只有 48 KB。为了使用扩展卡额外提供的 16 KB 空间开发者需要仔细安排地址、代码和数据想方设法把每一个字节都利用起来。今天一个数十 GB 的游戏必然拥有 FC 时代无法想象的画面、声音、世界规模和交互复杂度。然而巨大的容量并不能保证它带来的乐趣一定超过一个只有几十 KB 的游戏。这并不是说现代软件不应该变大。真正值得思考的是在过去很多年中开发者已经习惯了存储容量会自然增长、网络会不断变快。日志、缓存和临时数据只要暂时还放得下就很少有人追问它们为什么存在又将在什么时候被删除。昂贵的 SSD 不会自动创造优秀的软件内存短缺也不会让开发者突然变得更加聪明。但当硬件不再被默认会无限增长时那些曾经被忽视的工程问题便会重新浮出水面。我并不怀念那个需要在几十 KB 中反复腾挪的时代。真正值得重新拾起的并不是资源匮乏而是那种曾经十分自然、后来却逐渐消失的工程习惯知道每一个字节为什么到来也知道它将在什么时候离开。本期内容 | 前一期内容 | 全部周报列表近期推荐iOS 设备锁定后Swift Task 能运行多久(Swift Task lifetime when an iOS device is locked)这是一篇来自 Apple Developer Forums 的问答。提问者发现一个会串行执行多个 REST 请求的 Swift Task在设备锁定后有时能够正常完成有时会暂停甚至出现超时。DTS 工程师 Quinn “The Eskimo!” 解释道Swift Concurrency 完全运行在应用进程中一旦 iOS 挂起进程执行 Task 的工作线程也会停止Task 本身并不会获得额外的后台运行能力。这种机制在网络请求中尤其容易造成困惑。应用被挂起时正在进行的 URLSession 请求可能因连接失效而失败进程恢复后开发者看到的可能是请求超时或网络错误而不是 Task 从暂停处继续并顺利完成。Quinn 给出了四种处理方向设法延缓进程挂起、在应用即将被挂起时停止请求、允许请求失败并做好重试与错误恢复或者使用 URLSession 的后台会话。Swift 6.4 中的 anyAppleOS (anyAppleOS in Swift 6.4)过去为跨 Apple 平台共享的 API 声明可用性时开发者需要分别列出各个平台及其对应的系统版本。由于不同平台的版本号长期不一致这类声明不仅冗长也容易出现遗漏或版本填写错误。随着 Apple 各平台从 26 开始统一版本号Swift 6.4 引入了anyAppleOS允许开发者在available、#available和#if os(...)中以更简洁的方式描述适用于 Apple 操作系统的 API 与代码。Artem Mirzabekian⁠ 介绍了这一新语法并梳理了它与平台专属可用性、canImport以及Package.swift平台声明之间的区别同时说明了如何结合更具体的可用性规则处理部分平台延后支持或完全不可用的情况。可复用 SwiftUI 视图的设计剖析 (The Anatomy of a Reusable SwiftUI View)越来越多的开发者在构建可复用 SwiftUI 视图时都希望其 API 尽可能贴近 Apple 官方的设计方式但具体如何落地往往缺少一套清晰的方法。Alexander Weiß⁠ 给出了自己的设计思路首先判断组件属于描述功能概念的语义型组件还是具有明确视觉形态的规定型组件再梳理其语义、合法状态、数据流与交互行为。同时组件还应遵循 SwiftUI 的状态管理方式正确使用不可变值、Binding 与 State并将无障碍支持、环境值和视图身份稳定性纳入设计而不只是关注 API 的调用形式。文章通过 Card、Tag 和 Rating 三个示例展示了初始化方法、自定义 View Style 和环境值等具体实现。这套方法的核心并非简单模仿 Apple API 的外形而是让自定义组件在组合方式、状态流转、交互行为、无障碍支持和环境适配等方面真正融入 SwiftUI 体系。SwiftUI 环境默认值不稳定的隐性成本 (The hidden cost of unstable SwiftUI environment defaults)Entry宏让自定义环境值的声明更加便捷但如果直接将引用类型实例作为默认值例如Entry var logger AppLogger()就可能带来不必要的视图更新。每当 SwiftUI 没有找到上层注入的值而回退到默认值时都可能重新创建一个AppLogger。这些对象虽然功能相同却不是同一个实例因此 SwiftUI 会认为环境值发生了变化进而重新计算读取它的视图。Xcode 27 开始会针对这种写法发出警告让这个过去不易察觉的问题更早暴露出来。Natalia Panferova⁠ 使用_printChanges()直观展示了这一问题并给出两种解决方式将默认实例保存在稳定的外部存储中或者将环境值声明为 Optional再由应用入口显式注入。我在《SwiftUI Environment理念与实践》中也分析过这一问题。其根源在于Entry宏生成默认环境值的方式使默认表达式可能在回退访问时被重复求值。使用 visualEffect 为 SwiftUI ScrollView 创建纵向视差效果 (Make Vertical Parallax for ScrollView in SwiftUI)早期的二维游戏通过让不同图层以不同速度移动用很低的成本便营造出了富有层次感的视觉效果。但在 SwiftUI 中实现滚动视差真正麻烦的通常不是计算偏移量而是持续获取每个视图在ScrollView中的位置。过去这类效果往往需要结合GeometryReader、自定义坐标空间和状态传递来完成不仅代码量较大处理不当还可能引发额外的视图更新。visualEffect的引入大幅降低了这类效果的实现难度。codelaby⁠ 展示了如何直接在visualEffect中读取视图的几何信息并根据滚动位置应用offset无须额外维护状态也不会改变视图原有的布局。工具swift-span-algorithms: 给 Span 补上算法能力在处理连续内存时Swift 过去主要有两类选择使用Array、ContiguousArray等拥有存储的容器安全易用但可能伴随复制、引用计数或额外分配使用UnsafeBufferPointer等指针类型成本低、控制力强却需要开发者自行保证内存仍然有效并避免越界访问。Swift 6.2 新增的SpanElement填补了两者之间的空白。它是一段连续内存的非拥有视图不持有底层存储却能借助编译器追踪生命周期并通过边界检查提供安全访问。不过在 Swift 6.2 中Span 尚未拥有 Collection、Sequence 那般完整的算法能力。David Retegan 开发的 swift-span-algorithms 正是对这一缺口的补充。它为 Span、RawSpan、MutableSpan 和 InlineArray 提供查找、比较、修剪、分块、滑动窗口、切分及游标Cursor等操作。整个库零以无隐藏分配和常数级额外空间为设计目标在扩展算法能力的同时仍然遵守 Span 的生命周期约束。往期内容当 Linux 成为“空气”容器、Agent 与不再重要的“桌面之争” - #143SPI 加入 AppleSwift 迈向自举 - #142Swift 还让你 Excited 吗- #141WWDC 26AI 帮你看完了然后呢- #140 支持与反馈如果本期周报对你有帮助请点赞- 让更多开发者看到评论- 分享你的看法或问题转发- 帮助同行共同成长拓展 Swift 视野 邮件订阅| weekly.fatbobman.com 获取独家技术洞察 开发者社区| Discord 实时交流开发经验 原创教程| fatbobman.com 学习 Swift/SwiftUI 最佳实践