【ArchSummit】如何通过AIOps推动可量化的业务价值增长和效率提升?>>> 了解详情
写点什么

写了 3 万行 SwiftUI 生产代码后的经验:凡事皆有利弊

  • 2023-02-27
    北京
  • 本文字数:2881 字

    阅读完需:约 9 分钟

写了3万行SwiftUI生产代码后的经验:凡事皆有利弊

上手SwiftUI才几个小时,我们就爱上它了,甚至立刻就决定放弃跨平台代码库,并在 iOS 上改成完全原生的架构。1 月份上架 AppStore 的 timing.is 这个应用完全是用SwiftUI构建的。它的开发过程历时数月,要不是 SwiftUI 除了优点外还有很多缺点的话,这个时间还可以更短。(补充一下,这个应用开发阶段就上架 TestFlight 了,基于用户反馈的迭代过程也消耗了我们一部分开发时间。)在开发后期,我们甚至重新审视了当初的决策。最后,出于几个原因我们没有放弃它:我们已经走的太远了,整个计划还严重超期,重启的成本是无法接受的。但这不是主要原因——尽管小问题不断,但我们还是很喜欢它。起码过半的时间它很好用,所以这个决策还是正确的。但本文要谈的是剩下的那不到一半的情况。

 

首先声明,本文的主题并不是“SwiftUI准备好投入生产了吗?”因为我的答案很明确,这是肯定的!起码我们的情况是这样。我们的应用还是要满足非常复杂的需求而设计的(日历应用一般很简单,但我们不想做那种没什么用的东西)。换句话说,如果你构建的是没什么负载压力、比较省事的东西,那我打赌它用起来基本上会更舒服。我们最终成功了,但也做出了很多妥协。SwiftUI 本可以——而且真的应该——变得更易用,特别是考虑到它自 2019 年发布以来已经有过三个重大更新了。问题在于我们遇到的问题并不像我们的需求那样复杂,其实都是些基础的东西。

 

免责声明:我们面临的一些问题完全有可能存在我们不知道的解决方案。但总之我们已经查过了,也没找到办法。这里的重点是这些基础的细节本来应该可以正常用下来的。

ScrollView 地狱

 

深吸一口气吧。这是最让我们纠结的控件。日历应用中需要无限滚动功能,在 SwiftUI 里执行这个操作相对简单,但只能平滑向下滚动,因为尝试按需加载项目时向上滚动会导致明显的抖动。我在 StackOverflow 上问过这个问题,浏览量有两千次,很明显没有有效的原生方案。其实我去年在 WWDC 实验室和一位 SwiftUI 工程师说过这个问题,他们的建议是:1)创建一个 LazyVStack,这就要在两个方向上都做一个大得离谱的数据集;2)滚动到 Today 的 onAppear。这个办法很有创意,不幸的是 scrollTo 在 LazyVStack 中表现不可靠。它甚至经常会远离目标,偶尔会稍微偏离目标,很少落到正确的位置。还好我们最后找到了 Marc Palmer 的这篇精彩文章《你的 SwiftUI ScrollView 是否滚动到了错误的位置?》。引用其中一段:

 

最后我把它隔离了。如果 ScrollView 中有 ForEach,并且要滚动到的视图的 ForEach 主体结果包含其他视图,则 scrollTo(id...)不会滚动到有 id 的视图框架。

 

竟然是这样?

 

他的办法能用,但还是不够可靠(我们会用非常繁忙的日历数据做用户体验压力测试)。我们不得不重新设计,接受现实:在 Agenda 视图里,很遗憾目前你无法回到过去。

 

遇到这种情况的时候你有三种选择:战斗、认输或重新评估。我们很顽固,不肯认输。我们的建议是为战斗设定时间限制,不要拖延,确保开发可控。建议大家基于当前的限制重新调整设计,这样用户就不会察觉到出现了什么局限,而且你的解决方案会让他们感觉是有意为之的!我们解决这个问题的办法就是引入了追溯元素。

 

但 SwiftUI 的 ScrollView 还有其他一些痛点,其根源在于它无法优雅地处理冲突手势,也就是说滚动可能会因为干扰手势而中断,反之亦然。例如,我们希望每个日历项都可以点击和长按。这是开箱即用的功能,但 onLongPressGesture 的最短持续时间被框架忽略了,结果打开速度慢得发指,也没有干净的解决方案。还好 Daniel Saidi 最近提出了一个方案:他的 ScrollViewGestureButton 用了一个隐蔽的 ButtonStyle 来搞定了。类似地,我们的 Day 屏幕有一个水平分页器,该分页器由多个相邻的垂直滚动视图组成。为了让滚动和水平滑动同时工作,Ciaran O’Brien 还巧妙地使用了一个 ButtonStyle 来创建人为延迟,因为在 UIScrollView 上没有 UIKit 的 delaysContentTouches 等效项。

 

除了 ScrollView,我们还经常面临性能和状态方面的问题。当然我们的麻烦更有可能是自己造成的,我们严重依赖 Observables。所以说如果你的架构基础没那么牢固的话,中等复杂度的应用很容易遇到问题。视图经常无意义地刷新,在可以无限滚动的日历中这将导致明显的卡顿。你离触发一个 @Published 属性总是一步之遥。对于这个问题,Martin Mitrevski 的《SwiftUI 性能技巧》这篇文章是必读的。Oskar Groth 最近发的一则技巧推文也引起了共鸣。我们的办法是不再默认随意创建 Publisher,而是加了一堆条件:

 

  1. 如果在更改特定属性的值后必须刷新视图,请考虑为其提供一个 @Published 属性 wrapper。

  2. 如果没有其他可能同时更改的属性也需要刷新视图,则继续执行 @Published 分配。

  3. 如果有这样的属性,则寻找是否有更统一的方法来发布更改,比如说一个单独的 Publisher,一旦导致对象属性发生变化的活动完成,其值就会发生变化。或者采用 Martin 当时对 objectWillChange.send()的建议。

 

同样,@EnvironmentObject 很好用,但这种便利本身也带来了麻烦。如果一个视图引用任何内容,请确保它需要响应其中包含的每个 Publisher 的更改。否则,考虑把引用替换成对它完全关联的一个新对象。

 


之前:当我们大量使用 Publisher 时,滚动时有一大堆时间线插入内容

 


之后:时间线上的 Publisher 数量显著减少

 

一般来说,如果你像我之前那几次一样让自己陷入困境,请不要重蹈我的覆辙:就像我一开始天真地设置一堆不必要/非最优选择的 Publisher 一样,我的做法跟正确路径背道而驰,把那些看起来不重要的东西都删掉了。在你移除任何东西之前,请先研究这个属性的轨迹。我有几个移除示例并没有立即产生什么明显的后果,结果一段时间后视图没有响应特定场景中的变化,这时候问题才浮出水面。最后强调一下,请记住 @EnvironmentObject 将触发一个视图更新,即使视图没有引用其任何属性也是如此。一种找出不必要的重绘的廉价方法是将视图的背景颜色设置为 Color.random,这是 Peter Steinberger 给出的巧妙方法

 

TextField 也值得一提。我们遇到的一些新的磨合例子如下所述。

 

  1. 我们想移动入口 UI,打开一个表并立即关注标题 TextField,但是在弹出键盘之前有明显的延迟,所以这个操作取消了。

  2. 我们支持在不失去键盘焦点的情况下键入一个新条目并点击[Next]添加另一个条目。iOS 16 中的一个新错误意味着当你这样做时,键盘会在滑回原位之前就出现关闭动画。当前版本中保留了这个设计,因为我们觉得放弃添加连续项目的能力会比这个 UI 问题更让用户感到不爽。与此同时我们正在寻找解决方法,目前预计要到夏天才能解决问题。

  3. 当你在编辑条目时,我们希望标题字段的光标位置在开头,结果也做不到。

 

虽然有这些问题,但我们还是坚持使用 SwiftUI,对它也抱有敬意。我们经验丰富,不会让一些挫折,哪怕是频繁出现的挫折影响我们对整体体验的判断。考虑到我们的问题都很明确,相信它们最终都会被解决掉,只是具体的时间就没法预测了。与此同时,每当我们遇到障碍时,我们都会好好战斗。

 

timing.is 现已上架 AppStore,由 SwiftUI 打造。可在Twitter网站上关注作者了解更多开发经验。

 

原文链接https://blog.timing.is/swiftui-production-experience-problems-solutions-performance-tips/

 

2023-02-27 19:277873

评论

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

OSCS开源安全周报第23期:Foxit PDF Reader/Editor 任意代码执行漏洞

墨菲安全

开源 安全

华为云智能云接入ICA,助力企业轻松上云

与时俱进的时代

Dragonfly 和 Nydus Mirror 模式集成实践

SOFAStack

葡萄酒选择有技巧,贾斯特里尼&布鲁克斯皇室佳酿值得品尝

联营汇聚

书单 | 这几本书被输出到德国啦!

博文视点Broadview

【JVM规范】第一章 前言

四月

Java JVM

倒酒也是学问,贾斯特里尼&布鲁克斯葡萄酒专家教你如何倒酒

联营汇聚

测试监控和测试控制

FunTester

Vue + SpreadJS 实现高性能数据展示与分析

葡萄城技术团队

开源漏洞数量增长33%!企业安全债务不堪重负丨行业数据

SEAL安全

开源 开源安全 开源安全与治理

华为云智能云接入ICA,企业数据上云的信赖之选

与时俱进的时代

华为云全球加速GA:为您提供优质的网络服务

与时俱进的时代

统一观测|如何使用 Prometheus 监控 Windows

阿里巴巴云原生

阿里云 云原生 Prometheus

一文解析Spring JDBC Template的使用指导

华为云开发者联盟

开发 华为云 12 月 PK 榜

用优质俘获人心,贾斯特里尼&布鲁克斯葡萄酒成送礼首选

联营汇聚

Wallys/QCA9531,MIMO,2.4G,30dBm,2 x 2.4G MMCX//AR9344 802.11a/802.11n 5G

wallysSK

啊哈!缓存

孟君的编程札记

redis 缓存 cache canal Guava

盘点Python 中字符串的常用操作

华为云开发者联盟

Python 开发 华为云 12 月 PK 榜

Wallys/QCA9531,MIMO,2.4G,30dBm,2 x 2.4G MMCX//AR9344 802.11a/802.11n 5G

wallysmeng

安全可靠,弹性灵活--华为虚拟专用网络VPN

爱尚科技

有备无患!DBS高性价比方案助力富途证券备份上云

腾讯云数据库

数据库 腾讯云 备份 腾讯云数据库 富途证券

2022阅读总结

俞凡

阅读

组织上线 | 资源共享,协作自如

Jianmu

Docker k8s 镜像 容器镜像

开源 高性能 云原生!时序数据库 TDengine 上线亚马逊Marketplace

TDengine

数据库 tdengine 开源 时序数据库

《工业和信息化领域数据安全管理办法(试行)》2023年正式执行

行云管家

数据安全

企业数据安全解决方案-购买堡垒机!

行云管家

企业 数据安全 堡垒机

软件测试丨工具在接口测试中发挥什么样的作用?

测试人

软件测试 自动化测试 接口测试 测试开发

SpreadJS集算表联动数据透视表,高效实现前端数据多维分析

葡萄城技术团队

Excel 财务审核系统 #web

用Echarts实现前端表格引用从属关系可视化

葡萄城技术团队

基于U-Net网络的图像分割的MindStudio实践

华为云开发者联盟

人工智能 华为云 12 月 PK 榜

HMS Core 3D流体仿真技术,打造移动端PC级流体动效

HMS Core

HMS Core

写了3万行SwiftUI生产代码后的经验:凡事皆有利弊_语言 & 开发_Bardi Golriz_InfoQ精选文章