NVIDIA 初创加速计划,免费加速您的创业启动 了解详情
写点什么

我庆幸果断放弃了 SwiftUI:它还不够成熟

  • 2022-09-04
    北京
  • 本文字数:3049 字

    阅读完需:约 10 分钟

我庆幸果断放弃了SwiftUI:它还不够成熟

SwiftUI 很好,但是苹果对它投资不足。


在 2019 年的 WWDC 大会上,苹果推出了一个全新的 SwiftUI 框架,这是一个现代化的 UI 界面编码结构,它是基于 Swift 从头开始构建的。新框架使用声明性范例,让开发者用更少的代码编写相同的 UI。


SwiftUI 的愿景是降低开发 iOS 门槛,吸引更多开发者、丰富 iOS 的业态。并且 SwiftUI 可以“实现一次编码,可适应五端 Apple 产品平台”, 包括 watchOS、tvOS、macOS 等,以此统一苹果平台的 UI 框架。


苹果传递出来的消息就像是说:“SwiftUI 是一个了不起的用户界面框架,而且 100% 绝对会成为苹果平台上应用开发的未来。”


这些年,也有一些用 SwiftUI 重写 UIKit 应用程序的案例,去年奈飞新版 iOS App 的登录界面也完全由 SwiftUI 重构。



本文的作者 chsxf,是一家独立游戏工作室的首席开发,也是 15 年的苹果用户,他想尝试将 SwiftUI 放到自己的项目中,但是最终失败了。他发表了一篇博客,总结了尝试并放弃 SwiftUI 的过程,这篇文章在 Hacker News 上引发了开发者们的大量讨论:


“恕我直言,SwiftUI 是一个很好的机会,但苹果公司对它投资不足。这是一项很好的技术,响应式方法非常适合许多典型的基于视图的需求,但对如何处理边缘情况,文档中非常缺乏相关的说明。”


“这是个好主意,但 SwiftUI 的主要问题是完全不成熟。”“它具有复杂的行为,不适用于需要大容量或复杂 UI 的 App。”


“而且 SwiftUI 改进太慢了。”......


chsxf 的博客原文翻译:


最近,我手头正好有个“The Untitled Project”(名字还没想好)项目需要完成。考虑到配套创作工具 CiderKit 在发展成熟的过程中也变得愈发复杂,再加上创建各种窗口和 UI 元素的实际需求,我决定尝试用用 SwiftUI。这是个宝贵的机会,能让我认真体验一把 SwiftUI 并探索其内部工作原理。


起初项目工作良好,我对 SwiftUI 的表现可以说非常满意,我甚至创建了自己的修改器,以便更轻松地显示警报消息。但美好的甜蜜期很快过去,接下来我就要说道说道 SwiftUI 的那些“坏毛病”了。


实时检查器不好用


接下来,我开始了 SwiftUI 探索之旅的第二站——为地图编辑器创建实时检查器。跟其他创作工具一样,这款检查器的功能就是选定一个对象,并把可检查的对应属性显示在一个临时的用户界面元素当中。过程当中,Swift 协议和它处理泛型的方式也给我带来了不少麻烦,但这里我们就不过多展开了。


我还遇到了其他问题,因为 SwiftUI 高度依赖于 View 协议的实现结构,但 View 协议又有关联的类型,所以只能把它当成约束来用。好在配合 some 关键字和 opaque 类型等设计,我最终还是为可选对象找到了一种实现方法,让每个对象都能提供自身特定的 UI 元素。



之所以下决心选择 SwiftUI,就是因为初步测试时效果不错。如上图所示,地图编辑器位于左侧,检查器位于右侧。起初,我测试了一个 UI 元素,那是个用于开灯和关灯的勾选框。它运行良好,所以我根本想象不到后续会出什么大乱子。


但在开始实现更复杂的检查器视图时,特别是涉及带有/不带步进器或颜色选择器的多个文本字段时,整个运行速度开始剧烈下降。SpriteKit 视图一般都能以每秒 60 帧的完美速率呈现(只要用的不是英特尔孱弱的 iGPU)。但每当 SwiftUI 更新检查器视图时(这种更新可能出现在移动过程中,甚至是在输入文本字段的时候),渲染速率都会下降到每秒 10 到 15 帧,而且相当不稳定。这显然让人无法容忍。


我认真做了一番分析,并发现了几个问题。首先,由可选对象提供的视图在每次重绘时都是在完全重新创建。我虽然通过缓存稍稍提升了性能表现,但实际体验仍然非常糟糕。事实证明,SwiftUI 检查器视图就是没法提供合理的重绘速度。我在网上查找了解决方案,最后编写了一个延迟版本的 ObservableObject,由它来强制每秒只发布一次更改(参见以下代码)。


import Combine

import Foundation


extension ObservableObject { func delayed(_ delay: TimeInterval = 1.0) -> DelayedObservableObject<Self> { return .init(object: self, delay: delay) } }


@dynamicMemberLookup

class DelayedObservableObject<Object>: ObservableObject where Object: ObservableObject {

private var original: Object

private var subscription: AnyCancellable?


fileprivate init(object: Object, delay: TimeInterval) {

self.original = object

subscription = object.objectWillChange

.throttle(for: RunLoop.SchedulerTimeType.Stride(delay), scheduler: RunLoop.main, latest: true)

.sink { [weak self] _ in self?.objectWillChange.send() }

}


subscript<Subject>(dynamicMember keyPath: WritableKeyPath<Object, Subject>) -> Subject {

get { original[keyPath: keyPath] }

set { original[keyPath: keyPath] = newValue }

}

}


随着重绘频率的降低,终于能比较顺畅地操作地图上的对象了,每秒的帧率浮动一般就只有个位数。但这会导致检查器中的值出现延迟,因此在地图编辑器的交互过程中(比如使用移动工具时)结果不准确,所以效果还是称不上完美。


但我觉得这可能只是个独立问题,并不能因此把 SwiftUI 一棒子打死。所以,我打算继续探索。


越来越慢


在实现了第一个检查器之后,我开始研究另一个主题:Sprite 资产编辑器。利用这款工具,我可以用多个 sprite 拼接成复杂的资产,再最终为它们制作动画。它的显示效果就是主窗口中的一张表,出于学习的目的,我当然还是想继续用 SwiftUI 喽。毕竟初次尝试肯定会有种种问题,应该再给它一次机会。



如大家所见,这是个复杂的窗口,包含多种不同上下文(上方的「Sprite 资产数据库」列表,左侧的特定「Sprite 资产数据库」内容,以及其他与选定 Sprite 资产对应的编辑器元素)。我需要为每个上下文创建一个视图,这些视图同时又是其他视图的「子视图」,然后把需要的数据传递给特定视图。


但上图展示的效果其实是在 AppKit 中完成的,因为我在 SwiftUI 一直实现不了预期的功能。大家应该注意到了,中间的 SpriteKit 视图上有三个按钮(分别是+、200%和-)。这些按钮只跟管理 SpriteKit 视图缩放的 @State 相关联。尽管几乎不涉及任何其他数据,在界面更新前单击这些按钮,也会产生将近一秒钟的巨大延迟。我刚开始以为是因为地图编辑器的 SpriteKit 主视图仍在后台渲染。所以我尝试在工作表显示出来后禁用渲染,但结果没有任何改变。


变更从一种环境传播至另一环境时,我也遇到了类似的延迟问题。这可以说是压死骆驼的最后一根稻草了,我决定放弃 SwiftUI,继续用 AppKit。


总结


其实没能在项目中用到 SwiftUI,会让我感觉有点遗憾。我仍然觉得它是一项很棒的技术,只是可能不适合我的这个特定用例。但我真的不确定是不是自己的用法有问题。我打算在 Nihongo no Kana 的更新版本中再用用 SwiftUI,毕竟那款 iOS/iPadOS 应用的重绘频率低得多,所以应该不会有太大问题。


也许 SwiftUI 还没做好全面替代 AppKit 的准备。The Untitled Project 的 CiderKit 创作工具并不是作为 Catalyst 应用构建的,也不依赖于 UIKit。但继续使用 AppKit 的最大优点,就是没有任何延迟而且一切功能完全符合预期。当然,整个构建过程更繁琐,而且自动布局功能也不怎么好用。但我至少可以更好地控制应用程序的行为,而且根据需求随意调整各种元素。


总之,经历了这么一番波折,我还是很庆幸自己果断放弃了 SwiftUI。这可能是我在这个项目上做过的最明智的选择。


参考链接:

https://chsxf.dev/2022/08/28/5-tup-why-i-quit-using-swiftui.html

https://news.ycombinator.com/item?id=32630389

https://xie.infoq.cn/article/28af907f31baa7e7283a31ed4

2022-09-04 12:008528

评论

发布
暂无评论
发现更多内容

4月月更开奖啦!中奖者速来领取!

InfoQ写作社区官方

热门活动

备受关注的Bit.Store,最新动态一览

西柚子

时序数据库在监控运维平台中的应用

CnosDB

IoT 时序数据库 开源社区 CnosDB infra

关于数据一致性的理论

穿过生命散发芬芳

数据一致性 5月月更

知名金融数字化服务提供商南天信息加入龙蜥社区

OpenAnolis小助手

开源 金融数字化 龙蜥社区 CLA 南天信息

【愚公系列】2022年05月 二十三种设计模式(十一)-享元模式(Flyweight Pattern)

愚公搬代码

5月月更

玩了一场剧本杀,同车队友“不是人”

脑极体

架起医院就诊“快车道”,YRCloudFile 打造智慧 PACS 存储系统

焱融科技

云计算 分布式 高性能 文件存储 智慧医疗

在线TSV转纯文本工具

入门小站

工具

单源最短路问题

工程师日月

算法 5月月更

如何开发 LAXCUS 分布式应用软件(四):编写边缘端软件

LAXCUS分布式操作系统

并行计算 端边云协同架构 分布式操作系统 分布式应用软件

【刷题第五天】1. 两数之和

白日梦

5月月更

云图说|华为云帮助中心最佳实践:源自项目实战的上云指导

华为云开发者联盟

最佳实践 华为云 云图说 帮助中心 业务上云

深入剖析 split locks,i++ 可能导致的灾难

火山引擎开发者社区

Iframe的好处和坏处

恒山其若陋兮

5月月更

设计微博系统中“微博评论”的高性能高可用计算架构

高山觅流水

「架构实战营」

维护版式文档技术生态 国际PDF协会向福昕软件发来感谢信

联营汇聚

Cilium 多集群 ClusterMesh 介绍

Se7en

2021年国内促进软件产业发展十大事件出炉,HarmonyOS 2入选

科技汇

百万用户规模电商秒杀系统架构设计

「架构实战营」

快速上手 Pythond 采集器的最佳实践

观测云

运维 可观测性 可观测

linux之iftop命令

入门小站

Linux

druid 源码阅读 2——minEvictableIdleTimeMillis参数的实现逻辑

张大彪

备受关注的Bit.Store,最新动态一览

BlockChain先知

“软件定义汽车”的数字化之变,华为云低代码平台带来了什么?

脑极体

大数据的特点

奔向架构师

大数据 数据仓库 5月月更

Hadoop Echarts

Emperor_LawD

hadoop 5月月更

数据产品经理的价值管理

第519区

团队管理 项目管理 产品经理 数据产品经理 项目经理

在线HTTP请求头响应头转JSON工具

入门小站

工具

备受关注的Bit.Store,最新动态一览

小哈区块

论现象背后的驱动结构

凌晞

架构 结构化思维

我庆幸果断放弃了SwiftUI:它还不够成熟_大前端_核子可乐_InfoQ精选文章