-
AllTrails:无需重写,也能大步向前
跟随 AllTrails CTO James Graham 走进幕后,了解他的团队如何将 SwiftUI 融入其现有的 UIKit App。探索渐进式采用策略,让你在保留现有代码库的同时,更快地构建新功能。此外,了解 SwiftUI 与 UIKit 的全面互操作性如何使其成为你 App 的务实之选。
这个讲座最初作为“与 Apple 会面交流”活动“SwiftUI 基础知识:使用 SwiftUI 构建卓越的 App”的一部分讲授。请观看完整视频,以了解更多见解和相关讲座。
资源
-
搜索此视频…
大家下午好。 我有个问题想问大家。 也许你们能感同身受。 你们有没有仔细看过自己那些 老旧的 UIKit 代码库? 也许看到四年前写的一个庞大 视图控制器时,会心想:“天哪, 这得好好重构一下了。” 我们先把这个问题搁置一下, 直接用 SwiftUI 重写整个项目吧。 大家举个手。有没有人遇到过类似的情况? 哇,真多。我看到有非常、非常多的人举手了。 我相信还有更多在线观看的朋友。 这个想法确实很有诱惑力, 但在我们这样的规模下进行重写—— 无论是依靠工程师的努力, 还是借助 AI 原生工作流—— 都会带来巨大的风险。 大家好,我是 James Graham。 我是 AllTrails 的首席 技术官(CTO), 今天我想和大家聊聊我们如何在无需 重写代码的情况下实现现代开发速度。 我想向大家展示,我们是如何让 SwiftUI 自然融入我们的开发流程, 而不是自上而下强行推行。 在深入探讨之前,先来概述一下本次分享的框架。 首先,我将介绍 AllTrails 是什么, 我们的规模以及面临的限制。 接着,我将讲述 SwiftUI 如何 在无需重写代码或强制要求的情况 下融入我们的代码库。 之后,我将向大家展示那个转折点。 即 SwiftUI 何时 从实验阶段转变为默认选择。
最后,我将总结当前的现状, 以及我们如何看待自己的混合架构。
要理解我们的技术决策,就必须了解我们的规模。 如今, AllTrails 已成为全球最受欢迎、 最值得信赖的户外探索平台。 我们的使命很简单:帮助全 世界的人在户外找到自己的路。 无论是当地公园的散步, 还是为期数天的徒步旅行,我们都会通过 提供最新的路线详情以及“照片导览”等功能 (该功能会突出显示沿途的照片), 帮助人们发现路线、 自信地导航,并提升他们的户外体验。 我们为您提供全方位支持。 AllTrails 拥有超过 9000 万名社区成员。 我们在全球收录了 50 万条步道, 会员累计行走里程已超过 19 亿英里。
我们支持 14 种语言, 这意味着我们做出的每一项技术决策, 都会影响数百万使用不同设备、身处不同地区、 且关键的是处于不同网络连接水平的会员。
我们服务于兴趣和偏好各异的广泛用户群体。 一方面,有希望在当地湖畔享受美景、 进行轻松且相对平坦的午后 散步的休闲用户;另一方面, 也有正在挑战全天“半圆顶”徒步路线、 在没有手机信号的情况下 依靠离线导航的狂热徒步者。
这种多样性对可靠性、 电池续航和 UI 性能提出了严格的限制。 我们不能发布有缺陷的代码, 但我们的 App 并非一成不变。 它随着新界面的出现和新功能的深入而不断演进。
当 SwiftUI 问世时, AllTrails 已经是一个规模庞大、 成熟且成功的 UIKit App。 作为这种演进的一个例子, 以下是我们的主页在过去几年中的变化。
当时我们采用每周发布周期, 同时支持免费和付费两种使用体验。
关于我们的遗留代码,我要强调最重要的一点是: UIKit 并不是一个需要修复的问题。 它是支撑我们规模发展的基石。 正因如此,我们无法暂停 App 运行来进行改动。 我们必须在持续运营的同时,边走边升级。
当 SwiftUI 出现时, 它带来了我们迫切渴望的功能。 更简洁的状态管理视图会自动更新, 当数据发生变化时, 能消除所有因 UI 与模型 不同步而导致的错误, 同时减少代码量。 与 UKit 的等效实现相比, 代码量减少了 40%。 这意味着需要维护、更新和查看 实时预览的代码减少了 40%。 还能即时迭代设计变更。 但重写一款成熟的应用程序并非可行之选。 无论从技术还是组织层面来看都是如此。 我们需要一种不同的方法。 于是,SwiftUI悄然融入了我们的代码库。 这既不是强制要求,也不是路线图中的项目。 我们创建了 一个沙盒环境,将其用于原型和独立 服务的低风险实验。 这为我们提供了一个学习该框架的空间, 同时无需将我们的候选发布版本 押注于此。
最初的真正抉择并非在于选择 UIKit 还是 SwiftUI。 而是如何让它们协同发展? 我们很早就开始投入互操作性的开发。 请看这里的代码片段。
哦对了,这就是我们的桥接方案。 我们采用 SwiftUI 的特性, 比如我们的 Trail 协调器。 将其封装在托管视图中, 然后直接放入标准的 UIKit Stack View 中。
接着将其添加到滚动视图中。
当我们向下滚动页面时, 可以看到托管视图中包含 SwiftUI 子视图的某些部分。 我们很早就确立了这种模式。
这是一个流程层面的决策, 旨在确保边界清晰, 并使这两个世界随着 时间的推移能够使用同一种语言沟通。 两条并行轨道自然形成。 UIKit 仍然为我们处理 App 的生命周期导航等繁重工作, 以及像我们的路线页面或社区活动 视图这样复杂或深度集成的界面。
在成熟的 App 中,仅仅为了更换框架而 重写那些稳定且经过实战 检验的界面是没有意义的, 因此我们避免在徒步途中 重新规划一条标记清晰的路线, 而是专注 SwiftUI 方面能为用户带来 明显体验提升和开发效率提升的领域。 我们有意将 SwiftUI 应用于 那些独立且边界明确的界面中— —例如渲染复杂视图和进行新实验。
这里有两个相关示例。 路线评价流程是一个具有动态 状态和 UI 更新的自包含界面, 这与 SwiftUI 的声明式模型高度契合。
基于 Apple 智能构建的 “向路线提问”功能则是一项更新、 更具实验性的服务。 SwiftUI 让我们能够 在此快速迭代并优化体验,同时 避免与 App 的核心架构产生紧密耦合。
SwiftUI 在我们的设计 系统中同样大放异彩。 让我们在调试模式 下查看我们的 App,它可视化了名 为 Denali 的设计系统。 Denali 正在日益壮大, 我们已联合设计、系统、 工程等团队,确保 所有新功能都能充分利用该系统。
你们在这里看到的所有核心组件— —按钮、分段控件、控件、徽章— —都是我们设计系统的一部分, 可在 App 的调试模式下查看, 现在均由 SwiftUI 构建而成。
过去需要数百行样板代码才能实现的功能, 现在只需其中的一小部分。 而且当我们需要添加新变体或调整间距时, 只需进行一次简单的修改, 更改就会自动同步到所有相关位置。 SwiftUI 让我们能够在不 增加代码量的情况下扩展设计系统。
在采用 SwiftUI 时, 我们还注意到一个意想不到的变化。 它开始影响我们的架构。
视图模型变得更小了,通常缩减了三分之一, 因为我们不再需要编写仅用于保持 UI 与状态同步的粘合代码。
此外,由于 SwiftUI 替我们处理了状态传播, 我们编写的显式基础代码、发布器、 运算符以及生命周期管理代码也大幅减少。
而且,由于 UI、 状态和行为融为一体, 更改所涉及的代码行数更少。 我们的 UI 拉取请求体积 缩小了 30% 至 40%, 代码审查速度也明显加快。
正是从那时起, SwiftUI 不再像是一种 UI 实验, 而是开始被视为构建系统的正确方式。 我们从未强迫工程师使用 SwiftUI。 他们选择在新的项目中使用它, 是因为它减少了开发阻力。 它降低了认知负担。
当选择正确时,代码量减少 40%的采用率便会形成良性循环。
这就是我们得到的启示。
SwiftUI 的普及并非 因为我们要求大家使用它。 它之所以普及,是因为它是前进的最快路径。 当一个框架能降低认知负担并消除复杂性时, 工程师无需被说服。 我们自然会选择它。
但代码库并非一夕之间就发生了翻天覆地的变化。 它只是逐渐倾斜。UIKit 依然根深蒂固。 SwiftUI 正围绕它不断发展。 我们通过诸如“每项功能所 需代码行数减少”、“迭代
周期加快”以及 “孤立的 SwiftUI 功能中回归 问题减少”等指标来衡量成功。
我们意识到,互操作性就是基础设施。 我们投入资源开发了宿主封装器、共享动画、 桥接组件以及统一的主题系统。 当桥接机制稳固后, SwiftUI 就不再让人觉得是新事物, 而是成为了基础架构。
我们的 Apple Watch App 就是这方面的完美例证。 无论是“指南针”还是“地图”界面, 都是用 SwiftUI 构建的, 而我们的地图功能则使用了 MapKit。 这展示了我们如何利用现代架构来交付关键的、 对性能要求极高的功能。
我们选择 SwiftUI 并非因为它“很酷”, 而是因为它让我们能够在不触 及旧版导航逻辑的情况下, 更快地对复杂的界面进行迭代。
那么,我们目前处于什么阶段? 并非所有功能都已迁移到 SwiftUI。 您无需重写整个 App 就能享受其带来的好处。 我们是一个有明确发展方向的混合系统。
UIKit 提供了稳定性。 对于复杂的集合视图或 精细的导航栏等深度 UI 定制, 它依然表现出色。
SwiftUI 则定义了我们的发展方向。 它正在迅速迎头赶上, 而这里展示的植物识别功能就是 100% 基于 SwiftUI 实现的。
因此,我迄今为止所描述的一切, 都针对 AllTrails 的规模、 用户群体以及具体限制条件。 这是有意为之。 团队常犯的一个错误是将技术 采用视为非此即彼的决定。 “我们是否应该采用 SwiftUI?” 更好的思考方式是: “在什么条件下,采用新技术 能带来发展动力而非风险?”
当我们审视哪些做法真正对我们有效时 ——更少的 bug、 更快的交付、 更好的跨团队扩展性—— 这并非单一的技术选择, 而是一系列经过深思熟虑的决策。
我经常听到一个问题: UI Kit 和 SwiftUI 能否安全共存? 从我今天展示的内容来看,答案无疑是肯定的。 因此,与其告诉你们该采用什么, 我不如留给你们三个问题, 供你们根据自身情况评估采用决策。 第一个问题是:是否将互操作性视为基础设施? 如果互操作性 是脆弱的或临时拼凑的, 一旦面临实际产品的压力,采用进程就会停滞。 第二,新工具能否降低认知负担? 当一个工具真正简化了思维负荷时, 采用就不需要强行推动。 工程师们会自愿选择它。
第三,你关注的是发展势头,还是转换率? 发展势头体现在更小体积的拉取请求(PR)、 更快的代码审查以及更少的回归问题上。 而不是体现在你转换了多少代码库。
所以,如果这里有什么值得 借鉴的经验,那就是:你不 需要重写代码就能取得进展。 你需要的是方向。非常感谢大家今天抽出时间, 也感谢大家让我分享 AllTrails 的故事。 我们在山径上见。
-