写点什么

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

评论

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

Java程序员必须熟记的微服务面试题(含答案)

Java 程序员 后端

linux去掉空行的几种方法

入门小站

Linux

Java并发之Condition详解,springframework教程

Java 程序员 后端

Java的wait和notify学习三部曲之一:JVM源码分析

Java 程序员 后端

Java注解-一文就懂,java程序设计与实践教程王薇

Java 程序员 后端

Java岗大厂面试百日冲刺 - 日积月累,每日三题【Day16

Java 程序员 后端

Java日常开发的21个坑,你踩过几个?,java基础程序代码编写

Java 程序员 后端

Java架构师面试之Netty面试专题及答案(共10题,含详细解答

Java 程序员 后端

Java架构师面试问些什么?微服务之springcloud面试题(共22题

Java 程序员 后端

Java死锁的原因例子及解决方法,三年Java开发

Java 程序员 后端

Java版流媒体编解码和图像处理(JavaCPP+FFmpeg)

Java 程序员 后端

Java知识体系总结(2021版),JDK、JRE与JVM的区别与联系

Java 程序员 后端

Java岗大厂面试百日冲刺 - 日积月累,每日三题【Day14

Java 程序员 后端

Java开发工作4年还是只会“增删改查”,java技术栈太广

Java 程序员 后端

Java架构师职位常见面试题,看完面试不再慌!

Java 程序员 后端

Java岗大厂面试百日冲刺 - 日积月累,每日三题【Day26

Java 程序员 后端

Java岗开发3年,公司临时抽查算法,离职后这几题我记一辈子

Java 程序员 后端

033云原生之云服务测评指标体系

穿过生命散发芬芳

云原生 10月月更

Java并发关键字-final,java实战视频

Java 程序员 后端

Java注解,java架构师课程哪家好

Java 程序员 后端

Java常量池理解与总结,java线程池回收原理

Java 程序员 后端

Java开发必备 Git 分支开发:规范指南及完全学会Git的24堂课笔记

Java 程序员 后端

Java异常架构与异常关键字,海康威视java开发面经

Java 程序员 后端

【架构设计模块九】:设计电商秒杀系统

Ryoma

架构实战营第1期-毕业设计项目

Anyou Liu

「架构实战营」

java并发之Condition图解与原理剖析,推荐

Java 程序员 后端

文本行随机打乱工具

入门小站

工具

Java是未来的第一编程语言吗?,3分钟告诉你为什么要用Java开发高频交易系统

Java 程序员 后端

Java岗大厂面试百日冲刺【Day42】,java基础面试宝典

Java 程序员 后端

Java岗开发者福利!Github热度飙升的多线程学习手册,已获31k+star

Java 程序员 后端

Java市场饱和了吗?现在转行学习Java会不会太晚了?

Java 程序员 后端

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