2025上半年,最新 AI实践都在这!20+ 应用案例,任听一场议题就值回票价 了解详情
写点什么

我庆幸果断放弃了 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:009226

评论

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

【STM32】0.96寸OLED显示屏(7针SPI协议)软件模拟SPI

AXYZdong

硬件 stm32 2月春节不断更

第四周作业

Geek_72d5ab

LeetCode题解:297. 二叉树的序列化与反序列化,DFS,JavaScript,详细注释

Lee Chen

算法 大前端 LeetCode

【得物技术】走进Web3D的世界(1) 画个立方体吧

得物技术

html html5 js WebGL 得物技术

第四周作业-核销优惠券用例

隋泽

产品经理训练营

0期产品训练营第四周作业-学情周报用例

skylar

第四周学习心得

Trigger

极客时间 产品经理训练营

话题讨论 | 如何获得令人心动的前端offer

我是哪吒

程序员 面试 大前端 话题讨论 二月春节不断更

正确面对倦怠感,提升职场战斗力

boshi

职场成长 七日更

Java容器--2021面试题系列教程(附答案解析)--大白话解读--JavaPub版本

JavaPub

Java 面试 hashmap javapub

产品经理 - 第三周作业

LLL777

认识 Java 中的队列:Vector、ArrayList、CopyOnWriteArrayList、SynchronizedList

看山

Java 线程安全

产品经理训练营笔记 - 业务流程与产品文档(二)

.nil?

产品经理训练营

你真的了解 sync.Mutex吗

Leo叔叔

mutex Go Concurrency Patterns Go 语言

5G点亮工业革命前,2021需要持续点亮5G

脑极体

5. Python 循环的本质就是一段代码懒得重复写

梦想橡皮擦

Python Python Monad 2月春节不断更 python入门

流媒体传输协议之 RTP(下篇)

阿里云CloudImagine

音视频 流媒体 rtp

你看那个程序员,每年升职加薪,日赚3千

谙忆

「产品经理训练营」作业 04:知识星球加入星球用例

狷介

产品经理训练营

金融科技的碎片化思考(中)

曲水流觞TechRill

金融科技

区分重载和重写,轻松掌握 Java 多态

飞天小牛肉

Java 程序员 面试 后端 2月春节不断更

深入了解gradle和maven的区别

程序那些事

maven Gradle 程序那些事 构建工具

话题讨论 | 你是不是一个特别容易被说服的人?

石云升

话题讨论 2月春节不断更

从“乌鸡”到5G,不仅仅是谐音梗

脑极体

产品训练营 - 第四周 - 作业

邹小胖

产品训练营

ZEGO全新语音聊天室方案,2小时复刻 Clubhouse

ZEGO即构

极客大学·产品训练营·第三章作业(第四周)

二大爷

极客大学 极客大学产品经理训练营 产品训练营

马斯克说狗币牛逼,我说idea插件助你盯盘摸鱼

滑板上的老砒霜

比特币 idea插件 Android开发

第四章作业

Rui

抽奖小程序-活动发布用例分析及流程图

思亭

日记 2021年2月10日(周三)

Changing Lin

2月春节不断更

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