写点什么

SwiftUI 真的适合大规模应用吗?三家初创公司的实践经验

  • 2024-10-16
    北京
  • 本文字数:4805 字

    阅读完需:约 16 分钟

大小:2.28M时长:13:16
SwiftUI真的适合大规模应用吗?三家初创公司的实践经验

导读:在 SwiftUI 的舞台上,其以现代化的声明式编程语法和卓越的性能赢得了业界的广泛赞誉。然而,作为一个仅发展了四年的新兴框架,SwiftUI 仍面临一些亟待解决的挑战。尤为突出的是,导航管理的不足成为限制其广泛应用的绊脚石。尽管 UIKit 的传统导航方式已相当成熟,但如何在保持 SwiftUI 现代特性的同时,有效融合这两种框架,成为众多开发者面临的难题。

 

如何借助 Coordinator 模式破解 SwiftUI 中的导航难题。我们将通过实战演示,利用 UIHostingController 和导航协议,构建一套既高效又灵活的应用导航系统。此方案旨在融合 UIKit 的经典优势与 SwiftUI 的创新特性,为开发者提供一套完美的解决方案。无论你是 SwiftUI 的新手还是资深专家,本文都将为你奉上实用的技巧和深刻的洞见,助你跨越复杂应用开发中的导航难关。加入我们,一同探索如何在 SwiftUI 与 UIKit 之间搭建桥梁,解锁更多强大的功能潜力,让你的应用导航更加流畅、高效!

 

今天,我将与大家分享我在三家初创公司中运用 SwiftUI 的实践经验,并深入探讨我是如何克服那些阻碍你全面采用 SwiftUI 的关键挑战的。

 


本文改编自我于 2023 年 6 月在 Monzo 伦敦办公室举办的 NSLondon 演讲。

 

SwiftUI 的演进历程

 

自 SwiftUI 问世以来,我有幸一直是其主要用户。

 

作为 SwiftUI 的忠实拥趸,我亲眼见证了它的成长与发展。回溯至 2019 年,当 SwiftUI 的初版测试版横空出世时,我正投身于一个名为 Patcher 的副业初创项目之中,该项目旨在成为汽车维修领域的 Uber。如同许多初创团队的工程师一样,我们怀揣着满腔热情,决定在赢得首批用户青睐之前,先全力以赴地完善所有功能。

 

Patcher 应用功能繁多,涵盖了地图导航、新手引导、用户资料管理、工作请求处理、支付系统、维修流程跟踪等各个环节,甚至还包括了一个专为维修技师设计的配套应用。然而,这一项目的复杂性不言而喻,而那时的 SwiftUI,尤其是在 2019 年底至 2020 年初的阶段,显然还难以驾驭如此庞大的系统。

 

初期的 UIKit 与 SwiftUI 之间的互操作性并不理想,状态管理也成为了一项挑战,尤其是在整合像 Google Maps SDK(当时 SwiftUI 中的 MapView 尚未问世)和摄像头功能时,这些问题尤为凸显。更令人沮丧的是,SwiftUI 1.0 版本的导航功能几乎形同虚设,迫使我们不得不大量依赖模态视图来实现应用内的导航。

 

时间推移至 2020 年末,我参与了 Carbn 的创立,这是一家旨在引导人们培养更环保生活方式并减少碳足迹的初创企业。幸运的是,此时 iOS 14 已经发布,SwiftUI 也迎来了它的黄金时期。随着 @StateObject、惰性堆栈(lazy stacks)、ScrollViewReader 等强大工具的加入,以及我个人极为偏爱的 matchedGeometryEffect 效果的引入,SwiftUI 的功能与性能得到了显著提升。但更为重要的是,开发者们开始逐渐掌握如何利用 SwiftUI 构建出既合理又高效的应用程序。

 

现在,作为 Gener8 公司的移动工程负责人,我带领团队致力于帮助用户掌握并利用自己的数据创造价值。我们非常幸运地能够专注于 iOS 15 及以上版本的开发,这使得我们能够充分利用材质效果、可刷新视图、任务修饰符以及 Markdown 渲染等先进功能。

 

可以说,SwiftUI 已经全面成熟,从四年前的一个实验性工具,成长为能够支撑起严肃商业项目的强大 UI 框架。

 

它真的适合在生产环境中大展拳脚吗?

 

简而言之,如果你在生产环境中选择使用 SwiftUI,那么它绝对能够胜任!

 

回顾过去,Swift 在实现 ABI(应用程序二进制接口)稳定性之前,许多 Objective-C 开发者曾对 Swift 能否应对大型项目持怀疑态度。同样地,到了 2030 年,我们或许还会听到有人坚持推崇 UIKit(尽管我尚未见到有人仍在使用 Storyboard)。

 

接下来,我将简要分析 SwiftUI 的优势、存在的不足,以及为何一些工程师尚未完全全面转向 SwiftUI。

 

SwiftUI

优势

 

关于 SwiftUI 的优点,我不再一一赘述,仅简要提及几点核心优势。它速度快、采用声明式编程模式、具备高度响应性,并内置了单向数据流,让开发者能够轻松驾驭数据流向。无疑,SwiftUI 代表着未来的发展方向。

不足

 

当然,SwiftUI 也非尽善尽美。其导航功能稳定性一直是个问题,对于传统 UIKit 框架中的 AV(音频视频)、Camera(相机)和 Maps(地图,尤其是 iOS 17 之前的版本)等功能的支持较为有限。由于 SwiftUI 尚处于不断完善之中,有时开发者可能仍需借助 UIKit 来实现特定功能。此外,SwiftUI 的渲染引擎背后是 CoreAnimation、UIKit 和 Metal 的复杂交织,这种 “幕后魔法” 使得精确的性能调优变得更具挑战性。

 

难题

 

坦率而言,苹果似乎更倾向于将焦点放在单视图或主从结构上,对其他类型的应用架构则略显忽视。与所有新兴框架一样,SwiftUI 所依赖的操作系统版本不断变化,这意味着开发者需要在代码中频繁使用 if #available 语句来确保兼容最新功能。此外,SwiftUI 的状态管理机制与现有的 UIKit 代码体系存在较大差异,两者之间的无缝融合仍是一个待解的难题。

 

SwiftUI 虽然备受赞誉,但仍有其不足之处。作为一个仅诞生四年的新兴框架,某些缺陷在预料之中,然而,有一个核心问题显著地限制了它的广泛应用:

 

导航难题亟待解决

 

导航问题无疑是开发者在全面拥抱 SwiftUI 时遇到的首要障碍。特别是对于构建大型、复杂的应用程序而言,目前还缺乏一个理想的导航解决方案。虽然利用应用数据状态来控制导航逻辑在小规模应用中颇为有效,但尝试将这种方法扩展到大型项目时,其复杂性和工作量会急剧增加。开发者不应被迫去记忆整个应用的状态变化,以此推断出应该展示哪个界面。

 

问题的根源在于……

 

“大型视图控制器” 问题重现江湖

 

为了更深入地理解这一挑战,我们来看看苹果在 iOS 16 中对 SwiftUI 导航模式的改进尝试:

 

NavigationStackNavigationLink.navigationDestination.sheetNavigationSplitView

 

这些功能都紧密集成在 SwiftUI 的视图体系中,允许视图根据用户的交互行为或应用的状态变化来动态决定哪些子视图应该被呈现。

 

这迫使开发者不得不将视图与导航逻辑紧密绑定在一起,极大地挑战了代码的可测试性,也违背了工程开发的最佳实践原则。实际上,iOS 开发界的工程师们才刚刚挣脱出这种不良习惯的束缚。回想起过去,使用字符串类型的 segue、在 viewDidLoad() 方法中直接调用 URLSession、以及将整个应用逻辑堆砌在单个 Main.storyboard 文件中的日子,简直如同梦魇般让人心有余悸。

 

对于初涉 iOS 开发领域的你,若想深入了解 “大型视图控制器” 综合症的根源与影响,Paul Hudson 的这篇教程无疑是极佳的入门指南。它不仅深刻剖析了因关注点混乱导致的种种问题,还系统介绍了 UIKit 的最佳实践方法,为开发者指明了优化代码结构的路径。

 

导航逻辑的抽象化

 

我们已经明确了问题的症结所在:SwiftUI 尚不支持将导航逻辑进行有效的抽象化处理,这给大型工程团队在协作开发复杂应用时带来了不小的困扰。

 

但好消息是,解决方案并非遥不可及,它其实就隐藏在我们日常的编码实践中。

 


Coordinator 模式

 

这一模式,有时也被称为 Router 模式或 Navigator 模式,其核心思想高度一致:即将导航逻辑进行封装,实现与视图层的解耦。

 

我个人倾向于在 AppDelegateSceneDelegate 中引入一个 AppCoordinator 来实施此模式。该 Coordinator 作为整个应用的导航中枢,负责全局的导航管理,并可以根据需要创建多个子 Coordinator 来分别处理不同的业务逻辑流程。这些子 Coordinator 又可以进一步细化,管理更为具体的导航逻辑,从而形成一个层次分明、结构清晰的导航管理体系。

 


使用 Coordinator 模式的基础应用架构

 

Coordinator 的协议设计相对简洁明了:

 


Coordinator.swift

 

在 Coordinator 的设计中,Route 通常被定义为一个枚举类型,它列举了在该 Coordinator 所负责的导航流程中可能出现的各个界面。整个应用程序的导航逻辑都被巧妙地封装在 Coordinator 的 navigate(to:) 方法中。

 

尽管这种方法在 UIKit 框架中颇为常见,但接下来我将详细说明如何对其进行调整,以实现与 SwiftUI 框架的完全兼容。为此,我们只需引入两个额外的关键组件:

 

UIHostingController

 

UIHostingController 扮演了一个桥梁的角色,它将 SwiftUI 的上下文包裹在 UIViewController 之中,从而实现了这两种 UI 框架之间的无缝对接。

 

导航协议

 

为了进一步提升应用的灵活性和可维护性,我们将引入两个新的协议。这两个协议旨在抽象化 UINavigationController 和 UITabBarController 的功能,这两者都是构建复杂 UIKit 应用的基石。这样一来,无论你是否有 UIKit 的开发背景,都能轻松理解和应用你应用的导航结构。对于熟悉 UIKit 的开发者而言,这样的设计更是能够让他们迅速上手,并充分利用他们在 UIKit 中的经验来优化 SwiftUI 应用的导航体验。

 


通过 Coordinator 模式构建的导航架构

 

NavigationContext

 

此协议明确了我们对导航功能的基本需求:即能够展示和隐藏 SwiftUI 视图,同时将呈现逻辑与视图代码清晰分离。

 


NavigationContext.swift

 

在这个文档中,我们定义了一系列呈现函数,这些函数接收一个泛型视图作为参数,并实现了所有核心的导航操作,如推送、弹出视图,呈现和关闭视图,以及在导航结构的根位置创建初始视图。

 

协议的具体实现则是通过继承自 UINavigationController 的子类完成的:

 


MyNavController.swift

 

在这个文档中,你会发现实现过程与标准的 UIKit 导航方法非常相似,但多了一步操作 —— 即将 SwiftUI 视图包裹在 UIHostingController 中,以实现两者之间的无缝衔接。

 

接下来,我们可以轻松地为第一个 Coordinator 的 navigate(to:) 方法填充实现逻辑:

 


MyCoordinator.swift

 

NavigationRoot

 

此协议则是对 UITabBarController 功能的抽象,通常你的应用中仅会有一个这样的控制器,由 AppCoordinator 负责管理。

 


NavigationRoot.swift

 

尽管此部分不直接涉及 SwiftUI,但它负责管理多个导航上下文,并允许用户通过标签栏在不同上下文之间切换。此外,还提供了在标签栏控制器上模态展示上下文的功能,这在处理登录认证等场景时尤为实用。

 

拓展功能

 

在成功整合了所有主要构建块之后,我们不仅能够利用拥有 16 年历史的 Cocoa Touch API 来开发 SwiftUI 应用,还可以通过进一步子类化 UIHostingController 来增强功能。这样,我们可以:

 

  • 在每个视图上实现个性化的样式,无需依赖 UIAppearance API

  • 利用 UIViewController 的生命周期事件,在不同 NavigationContexts 之间传递状态

 

以下是一个基本实现的示例:

 


MyHostingController.swift

 

这一实现将引导我们创建一个更加复杂的 navigate(to:) 方法:

 


MyCoordinator.swift

 

通过利用视图的生命周期事件,在父视图上动态地显示和隐藏覆盖层。最终,我们展示了如何通过标准的 Coordinator 模式实现 UIKit 与 SwiftUI 之间的无缝互操作性,并以展示 SFSafariViewController 为例进行了演示。

 

结论

 

虽然 Coordinator 模式并非构建复杂 SwiftUI 应用的唯一途径,但它无疑是一个高效且实用的选择。当然,你也可以根据需求选择基于状态的范式,或者如果你的目标平台是 iOS 16 及以上版本,还可以考虑使用更新的导航 API,甚至尝试采用 The Composable Architecture 等先进架构。

 

这些不同方案的共同之处在于,它们都在不断推动技术发展。通过采用 Coordinator 模式,你可以在享受 SwiftUI 带来的响应式 UI 和快速开发优势的同时,充分利用 UIKit 在导航和定制方面的强大功能。此外,你还可以轻松实现即插即用的互操作性,几乎无需担心状态管理的问题。如果你尚未全面转向 SwiftUI,那么现在就是利用这些经过时间考验的范式的好时机,让我们从今天开始,迈向更加高效的开发之路。

 

作者简介

 

Jacob,现任伦敦一家初创公司的首席 iOS 工程师。

 

原文链接:

 

https://jacobbartlett.substack.com/p/swiftui-apps-at-scale

 

声明:本文为 InfoQ 翻译整理,未经许可禁止转载。

2024-10-16 16:139523

评论

发布
暂无评论

华为云数据灾备方案助力企业安全,守住企业底线

路过的憨憨

华为

重磅丨九科被评为“2022年中国流程挖掘行业典型实践厂商” 实力再获“RPA中国”认可

九科Ninetech

RPA 流程挖掘 数智化转型

云端软件运行,小程序安全沙箱技术为端侧安全保驾护航

Geek_99967b

小程序

软件测试面试真题 | 显式等待与隐式等待的区别?与强制等待的方式分别是什么,有什么区别?

测试人

软件测试 面试题 测试开发 测试工程师

HTML基本知识学习笔记

虾仁疙瘩汤

html 前端 10月月更

华为云CDN联手OBS桶,帮助企业更好降本增效!

路过的憨憨

华为

华为云数据灾备方案,撑起一把企业的保护伞

路过的憨憨

华为

开发者原来都是健身猛男?

InfoQ写作社区官方

热门活动

HTTP 常用的状态码及使用场景

孙铭

HTTP 10月月更 200

Spring Boot 应用使用 application.yml 和 application.properties 的区别

汪子熙

Java mvc spring springboot 10月月更

一文读懂云渲染“串流”全链路时延及优化策略

阿里云视频云

阿里云 云渲染 云游戏 串流

rdd pair reduce

小东

华为云桌面,安全可靠的云上办公首选

路过的憨憨

华为

助力企业资源的合理利用,华为云数据库RDS for MySQL使用经济更省心!

路过的憨憨

“程”风破浪的开发者|我是如何快速学 Go 的?GoFrame 只用了 3 天时间就从小白变大佬?

王中阳Go

Go golang 学习方法 10月月更 “程”风破浪的开发者

CSS学习笔记1

虾仁疙瘩汤

CSS html 10月月更

华为云数据灾备方案如何成为企业的坚实后盾

路过的憨憨

华为

华为云对象存储服务OBS教你一招轻松解决存储难题

路过的憨憨

华为

Vuex在uniapp中的使用

孙铭

Vue vuex 10月月更

Wallys /IPQ4019 IPQ4029 ,HTTPS / all the modules of Quectel/Indoor Aluminium alloy material

wallys-wifi6

IPQ4019 ipq4029

华为云对象存储OBS,助力企业高效解决存储问题

路过的憨憨

华为

View层、Controller层、Service层、Dao层的区别以及对应的功能

孙铭

service DAO 10月月更

英特尔“四维发力”系统级代工:晶圆制造、封装、芯粒、软件

科技之家

华为云灾备,保护企业信息数据势在必行!

路过的憨憨

华为

4000字深度总结!Pipeline五大性能实践,招招制敌

极狐GitLab

DevOps CI/CD 持续交付 pipeline 极狐GitLab

华为云数据灾备,如何让企业数据无忧

路过的憨憨

华为

Zookeeper的服务器的log4j升级为log4j2的升级方案(忽略配置化兼容问题)

洛神灬殇

zookeeper

HTML学习笔记

虾仁疙瘩汤

html 前端 10月月更

华为云大数据BI解决方案,如何帮助企业精准营销

路过的憨憨

华为

分布式事务-事务补偿(TCC)

zarmnosaj

10月月更

鸿蒙开发实例|分布式文件服务

TiAmo

华为 鸿蒙 10月月更

SwiftUI真的适合大规模应用吗?三家初创公司的实践经验_Android/iOS_Jacob_InfoQ精选文章