写点什么

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

评论

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

道与术丨华为云数据库战略启示录

华为云开发者联盟

数据库 opengauss 华为云 GaussDB 战略

面试官问我什么是扩展自适应机制

Java 程序员 后端

面试官:你如何利用-MySQL-Cluster-实现整体高可用?

Java 程序员 后端

面试官:多线程环境下,HashMap为什么会出现死循环?

Java 程序员 后端

阿里面试官整理出面试必问:java面试核心知识原理+框架笔记

Java 程序员 后端

震撼发布!阿里老兵亲手操刀微服务架构实战,整理出140个案例

Java 程序员 后端

面试官一口气问了MySQL事务、锁和MVCC,

Java 程序员 后端

阿里面试确实严格,面了整整5轮,还好我技高一筹!

Java 程序员 后端

面向对象-抽象性思想(知识整理)

Java 程序员 后端

面向对象设计的九大基本原则 (GRASP)

Java 程序员 后端

面试官求你别再问我hook了

CRMEB

面向对象知识点整理

Java 程序员 后端

教你如何用Keras搭建分类神经网络

华为云开发者联盟

神经网络 keras 分类神经网络 MNIST 数字图像

面试字节、阿里等大厂后,总结了今年的Java面试必问的微服务面试题(含答案)

Java 程序员 后端

面试官:如何提升TCP三次握手的性能?(1)

Java 程序员 后端

阿里蚂蚁金服超全126道面试题,都会的话,你也能去面阿里了

Java 程序员 后端

Flink CDC 实时数据同步详细解析

五分钟学大数据

flink 11月日更

震惊!2022 年秋招 Java 后端开发岗竟然一片红海!算法岗都不香了吗?

Java 程序员 后端

面试太难?技术面考察太底层?二面被拒到收割阿里架构offer,复盘成功经历分享!

Java 程序员 后端

阿里面试官:你好,谈谈对Synchronized的理解?(一

Java 程序员 后端

阿里面试官:就说最后一遍,有关Spring这13点我们必问!

Java 程序员 后端

MatrixDB 从 4018 个参赛项目中脱颖而出,荣获 HICOOL 全球创业大赛第三名!

YMatrix 超融合数据库

时序数据库 分布式时序数据库 Hicool

面试官:Java-线程池中的线程复用是如何实现的?

Java 程序员 后端

阿里面试官:HashMap 为什么是线程不安全的?

Java 程序员 后端

靠谱,这是我见过最好的编程指南了!赶快收藏吧,错过大学就白上了!

Java 程序员 后端

面试官再问分布式事务,求你看完这份至尊级分布式笔记,给年轻的面试官上一课

Java 程序员 后端

面试官最喜欢问的Spring Boot知识点整理【附解答】(下)

Java 程序员 后端

面试中常见的问题总结

Java 程序员 后端

面试前夕,你一定要先来看看阿里和京东都问些啥!(阿里+京东Java岗面试题概要

Java 程序员 后端

面试大厂一定离不开的——ThreadLocal,它的实现原理你知道吗

Java 程序员 后端

面试官都爱问的Spring源码:Spring与Mybatis高级整合

Java 程序员 后端

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