上周,React Native 核心开发者 Krzysztof Magiera 的一条 推文 在苹果开发生态中引发热烈讨论。推文中他展示了一个示例:在主线程阻塞的情况下,UIKit 的一个往返动画仍能正常执行,而 SwiftUI 的动画则停滞了。他认为原因在于 SwiftUI 没有使用 Core Animation 的 render-server animation,而是在应用进程中推进动画,因此对 SwiftUI 是否足够“原生”提出了质疑;随后又表示,在 React Native 中,开发者仍可通过 Core Animation 创建不受主线程阻塞影响的动画。
SwiftUI 共同创造者、早期核心开发者 Kyle Macomber 对此回应了不同的 观点。他解释说,SwiftUI 在设计之初就有意放弃了 Server-side Animation(由 Core Animation 在 render server 中推进动画),而选择让动画值在 client / app process 中计算和推进,以换取更好的可中断性与交互性。这并非技术上的退步,而是一项设计取舍(SwiftUI 并没有放弃 Core Animation,它仍然参与后续的渲染过程,改变的只是动画由谁来推进)。
动画的易用性一直是 SwiftUI 的标志性特色之一,近年来不断完善的弹簧动画,又进一步提升了动画衔接的自然程度和交互表现力。作为一个响应式框架,把动画推进放在 client 端,我认为完全可以理解。当然,代价也很明确:正如 Krzysztof 的示例所示,当动画推进依赖应用进程时,它更容易受到主线程繁忙的影响,也会占用更多 client 端的计算资源。
不过,client-side animation 并不意味着所有插值计算都必须发生在主线程。随着 SwiftUI 不断演进,部分动画以及 Shape 等计算密集型工作已经可以移到后台线程,以减轻主线程的压力。不过,由于动画的推进仍然属于应用进程,异步计算也受到状态更新和渲染流程等条件的限制,因此当主线程被完全阻塞时,动画依然可能停滞。尽管存在这种“弊端”,但从 iOS 18 开始,UIKit 和 AppKit 也开始采纳这套 client-side animation 模型:新的动画 API 可以直接使用 SwiftUI 的 Animation,并由同一套动画基础设施驱动,而不再生成传统的 CAAnimation。
本次讨论呈现出一个有趣的场景:表面上在争论 SwiftUI 是否“原生”,实际展现的却是两种框架设计理念的差异。
作为苹果面向未来的 UI 框架,SwiftUI 在设计时选择走出既有路径,放弃对 Core Animation render-server animation 模型的依赖,以换取更灵活、更强大的动画表达能力。事实上,Core Animation 的创造者 John Harper 本人也是 SwiftUI 的共同创造者之一,这项选择显然不是因为不了解 Core Animation,而是在理解其优势与局限之后做出的架构决定。这还带来了一个隐形优势:SwiftUI 的部分核心能力与苹果平台的具体实现得以进一步解耦。尽管苹果并未将 SwiftUI 开源,但从架构上看,它的一些核心思想并不天然局限于苹果平台。
而从 React Native 的角度看,Krzysztof 的表述也有其合理性。作为一个跨平台框架,React Native 的重要目标之一,就是在不同平台上尽可能利用平台已经提供的能力,让应用在行为和性能上更像“本地人”。
一个最初围绕“什么才算原生”的讨论,最终引出了两边核心开发者对 UI 动画架构的理解与阐述。作为一个吃瓜群众,我也从中收获不少。
原创
从 pbxproj 到 xcproj:Xcode 工程配置迎来 JSON 格式
最近一直在等待 Xcode 27.1 的到来,以便在 iPhone Duo 的模拟器中测试一下应用的适配情况。有意思的是,27.1 没等到,苹果率先发布了 27.2 beta。尽管 iPhone Duo 模拟器依然缺席,但新增的 project.xcproj 令人眼前一亮。它替代的是 .xcodeproj 包里的那份构建图 project.pbxproj,而不是整个工程包。它有哪些亮点,解决了什么问题,又有哪些事情没有改变,本文将对此进行探讨。
近期推荐
为 Devin 构建云上 Mac 开发环境 (How we built Devin’s Mac)
当 AI Agent 在本地运行时,权限早已授予,工具链已经就绪,遇到弹窗也可以人工干预。搬到云端后,这些理所当然的条件都需要重新构建。换来的好处并不在于能做到本地做不到的事,而是让开发环境本身成为可以分配给 Agent 的资源:任务可以异步运行、彼此隔离,闲置时暂停并保存状态,需要时再恢复,也更容易支持多个 Agent 并行工作。Jon Kelley 在文中介绍了为 Devin 构建 macOS 云端环境的过程,涵盖基于 Virtualization.framework 的虚拟机、磁盘快照、网络隔离、环境与权限预配置、桌面与 Simulator 的实时串流,以及借助无障碍树实现的计算机操作。最后一点对苹果开发者尤其有启发:做好无障碍支持,也是在为 Agent 的自动化验证提供结构化接口。
iOS 27: CrashReportExtension Framework
传统的崩溃处理有一个棘手的问题:当 signal handler 开始工作时,它所在的进程本身可能已经处于不可靠状态,内存可能损坏、锁可能没有释放,很多平时使用的 API 也无法安全调用。iOS 27 新增的 CrashReportExtension 改变了这一执行模型。当 App 崩溃后,系统会启动一个独立的 Extension,并通过只读的 CrashedProcess 让它检查已经崩溃的进程。
Anton Gubarenko 在文中完整演示了这个扩展的创建与使用,也给出了几条务实的判断。该 API 是对系统 .ips 报告的补充而非替代,适合需要自有后端、业务上下文关联或设备端预分析的团队。扩展本身应尽量精简,只负责生成并持久化报告,上传、重试等工作交给主应用,两者通过 App Group 共享存储。对多数独立开发者而言,系统崩溃报告加上 MetricKit 依然足够。
SwiftUI 动画的运作机制 (How SwiftUI animation works)
对于动画,开发者只需改变状态并设置动画函数,剩下的工作便交给 SwiftUI。但这些声明最终是如何转化成屏幕上一帧帧连续变化的画面?Kyle Ye 从 Transaction 和 AttributeGraph 出发,结合 OpenSwiftUI 的开发经验,逐步分析 SwiftUI 如何创建动画上下文、计算中间状态,并在动画过程中持续更新视图与布局。
与将动画插值主要交给 render server 的传统方式不同,SwiftUI 的许多动画需要在应用进程中持续推进。由于插值发生在视图图内部,任何满足 Animatable 的值都能被采样,布局、Shape 等更多状态因此得以参与动画,而 render server 主要处理图层属性。文中对异步提交路径中 CABasicAnimation 的分析尤其值得一读:它只承载当前帧的采样结果,下一帧便会被替换——屏幕上出现了 CA 动画对象,并不意味着动画的时钟已经交给了 Core Animation。这也为本期周评中提到的 SwiftUI 动画是否为“原生”之争提供了更多细节。
为 iPhone Duo 准备的新 API (SwiftUI: Some New APIs/ContainerViews Useful For Duo!)
iPhone Duo 的多种使用形态给 Toolbar 带来了新的挑战。为了充分利用更宽的屏幕并保留纵向内容空间,系统会在部分形态下将原本位于顶部和底部的控件移到屏幕侧边。但传统 Toolbar 中的文本按钮、自定义视图等内容并不一定适合纵向排列,Toolbar 与 Tab Bar 还可能需要争夺有限的侧边空间。iOS 27.1 为此增加了 ToolbarItemAxisBehavior、ToolbarVerticalBehavior、ToolbarVerticalCompressionBehavior 和 toolbarVerticalEdge 等 API,让开发者可以进一步控制 Toolbar 的方向、位置以及空间不足时的压缩策略。Itsuki 通过代码和演示展示了这些新 API 的具体行为。
这批 API 的共同特征,是让开发者声明偏好而非指定结果。面对 Duo 带来的多种形态,与其针对特定设备状态编写布局,不如描述内容之间的关系和布局偏好,再由 SwiftUI 根据实际环境决定最终呈现方式。
iPhone Duo 上的 Sheet 与折痕规避 (Sheets and fold avoidance on iPhone Duo)
当 iPhone Duo 处于部分折叠状态时,中央折痕形成了一块不适合交互的区域,按钮等控件如果恰好落在这里,会变得难以操作。Sarun Wongpatcharapakorn 以 Sheet 为例,展示了同一个界面如何在 Duo 的关闭、展开、横屏、竖屏以及部分折叠等不同形态下调整尺寸、位置和 Toolbar。Sheet、Alert 和菜单等系统组件已经内置折痕规避能力,无需开发者干预。因此对于开发者而言,优先使用系统组件,也意味着可以直接获得这些针对不同设备形态的自适应行为。
工具
Vapor 5 Beta 发布
在 Vapor 诞生十周年、距离 Vapor 4 发布六年半之际,社区终于迎来了 Vapor 5 的首个 Beta 版本。此次几乎是一次彻底重写——历时两年、变更约 49,000 行代码。最核心的改动是彻底告别 EventLoop 与 EventLoopFuture,全面转向 Swift Concurrency,并以结构化并发为基础,NIO 也基本从公开 API 中隐去。
同时,Vapor 5 进一步整合 Swift HTTP Types、Swift Configuration 和 Swift Service Lifecycle,并加入实验性的 VaporMacros,为路由和认证提供更多编译期类型检查。Vapor 5 要求 Swift 6.4,目前仍处于 Beta 阶段。有关更多变化,可以查看 Tim Condon 的 What’s New in Vapor 5 Beta。
Hinge:命令行调节 Duo 模拟器折叠角度
Xcode 27.1 的 Device Hub 可以调整 iPhone Duo Simulator 的折叠角度(按住 Option 键可见),但 simctl 和 devicectl 并没有提供对应的控制命令。Artem Novichkov 开发的 Hinge 补上了这个缺口,可以通过命令行设置 0–180° 的折叠角度,也支持打开、关闭、半折叠以及连续改变角度等操作。Hinge 还提供了 Agent Skill,让 Coding Agent 可以在测试过程中主动改变 Duo 的折叠状态并验证 App。
Enriched Markdown:不依赖 WebView 的跨平台 Markdown 渲染与编辑方案
在移动端实现 Markdown,常见方案是套一层 WebView。Software Mansion 推出的 Enriched Markdown 选择了一条更彻底的路线:基于各平台的原生文本能力完成 Markdown 渲染与富文本编辑,并可以将编辑后的内容重新输出为干净的 Markdown。
它提供独立的 SwiftUI SDK,基于 TextKit 2 与 md4c 实现 Markdown 渲染,同时也覆盖 React Native、Android 和 Web。相比单纯追求“能显示 Markdown”,Enriched Markdown 更重视原生体验:文本可以正常选择,链接、图片与代码块能够接入系统菜单,并适配 VoiceOver、TalkBack、动态字体和从右到左书写(RTL)。