Issue #155

iPhone Duo 留给开发者的一个月

Cover for Weekly Issue 155

包含 iPhone Duo 模拟器的 Xcode 27.1 beta 于 9 月 18 日发布,距离 10 月 23 日正式发售仅五周。颇为微妙的是,彼时不含 Duo 支持的 Xcode 27.2 beta 已先行推出——想适配苹果最新的设备,反而要使用版本号更低的 Xcode。这种“双轨 Beta”或源于 Duo 独立的系统版本与发布前的保密需要,代价则是开发者的准备窗口被压缩到了一个月左右。

开发者可以在 Device Hub 中开合、旋转、折叠这台虚拟设备。但任何有经验的工程师都清楚:模拟器能够还原几何形态与姿态,却无法还原可供性与真实的物理交互。

在缺乏实体反馈的这段真空期,开发者很容易滑向两个极端:要么无所适从,不敢越雷池一步;要么对着虚拟窗口凭空构想出许多看似精巧、在真实握持下却别扭难用的专属交互。

面对紧迫的倒计时,更务实的做法是先为“适配”划定理性的边界,分清什么是“够用”,什么是“好用”。

Apple 已明确表示,现有应用即使不重新构建也可以在 Duo 上运行,但“能够运行”显然不等于“已经适配”。确保应用在新设备上自然、稳定地呈现,是上市前“够用”的及格线;至于什么才是真正属于折叠设备的“好用”体验,恐怕要等设备来到开发者手中后,才会逐渐浮现答案。

10 月 23 日,或许才是 Duo 适配真正开始的那一天。

原创

ArrangementView:谋而后定

第一次看到 ArrangementView 的 API 时,我有一种说不出的别扭感。直到在 Xcode 27.1 beta 中实际使用后,这种感觉才逐渐清晰。查阅更多的苹果资料,再把它放回 iPhone Duo 的使用场景,我发现这份别扭其实来自两个方面:一部分源于我对它的定位不够清楚,理解之后便释然了;另一部分则来自 API 本身,即便想明白了,依然存在。

因此,想用好 ArrangementView 需要在使用前多想一步:内容之间究竟是什么关系?哪些结果可以交给容器,哪些取舍仍须由应用承担?谋而后定。

近期推荐

What’s new in Swift 6.4’s from Apple’s engineers

Xiangyu 以 WWDC26 Swift Group Lab 上 Apple 工程师的现场答疑为线索,逐一对照 Evolution 提案、编译器源码与官方文档,厘清了哪些能力已经落地、哪些仍处于过渡阶段,并订正了现场回答中若干版本与实现细节上的出入。

其中最耐人寻味的,是“如果重来一次”这个话题:早期的 Swift Concurrency 会让 nonisolated async 函数自动离开调用者所在的 actor,转到全局并发执行器(Global Concurrent Executor)上运行;经过数年的实践,Swift 团队认为,让它默认留在调用者的 actor 上才更为合理——若能从头设计,他们希望一开始便是如此。尽管这一行为在 Swift 6.4 中仍需显式启用,却清晰地展现了 Swift Concurrency 如何依据真实的迁移经验,不断校准自身的默认选择。


withTaskCancellationShield: Swift 6.4 new feature

Swift 6.4 引入的 withTaskCancellationShield(SE-0504)可以让一段代码不受所属任务已有取消状态的影响,非常适合必须完成的收尾和清理逻辑。它并不会撤销取消本身,只是让屏蔽范围内的代码(包括其中创建的子任务)暂时“看不到”取消状态;离开屏蔽区域后,原有取消状态会重新可见。由于该 API 依赖随系统分发的并发运行时(Concurrency Runtime),因此只能在 27 系列系统上使用,很遗憾无法向下部署。

Omar Elsayed 在文中给出了一个过渡方案:创建一个非结构化任务(Unstructured Task)并 await 其 value。由于取消状态不会自动传播到这个新任务,而等待 value 又能让当前任务等到清理结束后再继续,因此可以实现近似的效果。Omar 也强调,这只是权宜之计:不仅存在额外开销,还要求捕获的值满足 Sendable;待最低部署版本提升后,仍应改用正式 API。


Running iOS Background Tasks Reliably, Part 2

在 Part 1 中,Irving Popovetsky 通过长期的真机观察,总结出一套提升 BGTaskScheduler 可靠性的实践,但有一个问题始终无解:当用户较长时间不打开 App,后台任务获得的执行机会便会逐渐衰减。

这一次,他从 WWDC20 的一句提示中找到了突破口——系统不会依据 App 的使用频率来限制静默推送(Silent Push)。于是,他让 Cloudflare Worker 每小时向 CloudKit 公共数据库(Public Database)写入一条 WakePing 记录,再借助 CKQuerySubscription 触发静默推送唤醒 App。整个过程无需维护设备令牌(Device Token),也不触及任何用户数据。经过数月的实际运行,即便连续三周未打开 App,同步依然能近乎准点地按小时触发。这当然无法让 iOS 的后台执行变成真正的 cron,却很好地示范了如何将 BGTaskScheduler、CloudKit 与后台通知(Background Notification)组合起来,切实提升后台任务的可靠性。


拆解 Xcode 27 mcpbridge:Apple 没有用 swift-sdk,而是自研了一整套 MCP 栈

尽管 Apple 也参与了维护官方的 Swift MCP SDK,但 Xcode 27 自身的 MCP 实现却另辟蹊径。Snow Wu 导出并核对了 mcpbridge、mcp-server 及相关私有框架的符号表,发现从 JSON-RPC、MCP 语义层到命令行参数解析,Xcode 几乎全部采用自研实现。

这种“另起炉灶”并不只是为了规避第三方依赖。mcpbridge 本身甚至不包含任何工具定义,只负责在外部 Agent 与 Xcode 之间完成协议转换与路由,真正的工具能力仍由 Xcode 提供。再结合三进程架构、XPC 通信与三层权限闸门,可以看出 Apple 并不只是在 Xcode 里嵌入一个 MCP Server,而是将 MCP 视为一个系统架构问题:如何让不可信的外部 Agent 安全地接入一个状态复杂的 GUI 应用。


Apple Watch brings distributed system headaches to your app

当 iPhone 与 Apple Watch 都能读取并修改同一份状态时,你面对的其实已是一个小型分布式系统:两个独立节点随时可能失联,各自继续修改数据,并在数小时甚至数天后才重新连接。Jacob Bartlett 从这一视角重新审视 Watch Connectivity,借 CAP 理论剖析各个 WCSession API 背后的取舍:sendMessage 要求对端即时可达,以牺牲分区时的可用性换取实时、可确认的交互;updateApplicationContext 与 transferUserInfo 则容忍短暂的不一致,优先保证可用性,前者只保留最新状态,后者按序投递每一次变更。

这种视角也让“该选择哪个 Watch Connectivity API”从接口能力问题变成了产品语义问题:登录状态、设置同步、录音控制等数据,对一致性和可用性的要求并不相同,同一个 App 的不同功能完全可以做出不同选择。


iPhone Duo 和 AnyOS 27 适配

  • codelaby 介绍了 iOS 27.1 新增的 Reserved Regions API,以及如何通过 GeometryProxy 获取折痕(division)和摄像头遮挡(occlusion)区域。对于系统容器无法自动处理的自定义布局,它提供了一种避免重要内容落入折痕或摄像头区域的可靠方式。
  • Antoine van der Lee 从实际适配出发,系统梳理了 iPhone Duo Simulator、可调整布局、ArrangementView、Reserved Regions、铰链状态和垂直工具栏等新能力。核心思路并不是为 Duo 单独设计一套界面,而是先让应用真正适应可变空间,再用 Duo 专属 API 对特殊场景进行增强。
  • Ronnie W. 发现 macOS 27 会重新决定 SwiftUI Toolbar 的分组,即使使用 ToolbarSpacer,原本希望分开的项目仍可能被合并进同一个 navigation island。文章给出的解决方案相当直接:在需要精确控制分组时,让 AppKit 的 NSToolbarItemGroup 接管 macOS 工具栏结构。
  • Anton Gubarenko 整理了两场 iPhone Duo Group Lab 中 Apple 工程师对开发者问题的回答(第二部分),涵盖自适应布局、折叠姿态、垂直工具栏、多窗口、摄像头、无障碍以及 Simulator 等大量实际问题。贯穿其中的建议十分一致:把 Duo 当作拥有更多可变空间的 iPhone,优先依赖系统容器和自适应布局,只有确有需要时再介入折痕、铰链和特定姿态。
  • Sarunw 解释了 iPhone Duo 上系统如何决定 Toolbar Item 应进入水平还是垂直工具栏:为项目同时提供图标和标题,让系统根据空间选择表现形式;只有在默认判断不合适时,才通过 axisBehavior 明确指定 .horizontalOnly 或 .verticalPreferred。

工具

PaintKit:Swift 栅格绘图与图像处理引擎

Josh Lin 开发的 PaintKit,是 ItsPaint 项目中一套可以独立使用的 Swift 栅格绘图引擎,涵盖像素存储与混合、光栅化、画笔与形状、选区、撤销、文字渲染,以及图片和 PDF 编解码等能力。引擎以预乘 Alpha 的 RGBA8 像素为基础,每次编辑都会返回实际发生变化的区域,使画面刷新和撤销记录都只需处理必要的像素;撤销历史也不是简单限制操作次数,而是通过局部像素补丁按实际内存占用管理。PaintKit 不依赖 AppKit、SwiftUI 或第三方库,采用 Swift 6 严格并发模式,大部分功能无需启动应用或 Xcode 工程即可通过 swift test 验证。

ItsPaint 则是 PaintKit 的完整应用与实践验证。它是一款面向 macOS 的轻量级原生绘图及截图标注工具,提供铅笔、画笔、形状、文字、选区、像素化和聚光灯等 13 种工具,支持 15 种形状、九种导出格式和 PDF 标注,在不使用机器学习模型与网络服务的情况下移除简单背景。ItsPaint 同样开源,并可在 App Store 免费下载。


SwiftFairy:让 Agent 不再“读过就忘”的 SwiftUI 审查工具

由 Natalia Panferova 与 Matthaus Woolard 打造的 macOS 工具,通过本地 MCP 服务器为编码 Agent 提供 Swift 与 SwiftUI 代码审查。

与把大量规则写进 Skill 或 AGENTS.md、再期待 Agent 在正确的时候想起它们不同,SwiftFairy 将“发现问题”交给确定性的静态分析:它把代码解析为局部图,与声明式的问题模式进行匹配,将发现精确定位到代码行,再把对应的解释和修复示例交给 Agent。知识只在真正命中问题时才进入上下文,既避免了 Agent “读过就忘”,也减少了 Token 消耗。

SwiftFairy 的价值也很大程度来自规则本身。目前这些知识以“卷轴”(Scrolls)的形式组织,首批包括 Proper SwiftUI、Performant Swift Charts 与 Modern Swift,内容来自两位作者多年积累的书籍与文章,以及 Natalia 曾参与 Apple SwiftUI 核心团队工作的经验。与让另一个 LLM 来 Review 相比,SwiftFairy 更像是在 Agent 工作流中加入了一层具有领域知识、结果可重复的静态审查。

相关周报

订阅 Fatbobman 周报

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

立即订阅