从 iOS Basecamp 3 探索混合应用开发的最佳实践

时间:2024-03-30 07:01:52

原文:Basecamp 3 for iOS: Hybrid Architecture
作者:Zach Waugh
翻译:陈冬
审校:苏宓


Basecamp 是 37signals 公司旗下的一款非常流行的基于云服务的项目管理软件。以简单易用和颠覆性的创新而出名。

笔者曾写过很多篇关于如何构建混合移动应用的文章。Basecamp3 项目代表了混合移动架构的最新版本,并且它从过去的版本中吸收了很多精华。

Basecamp 2 项目的第一个 App 仅仅适用于 iPhone,它是利用 RubyMotion 作为 UIWebView 的封装器来进行编写的。之后,我们又通过 Xcode+Objective-C、UIWebView 以及更多原生代码的方式去构建一个通用型的 App。对于 Basecamp 3,我们使用 Swift 和 WKWebView 代替 Objective-C 和 UIWebView,新增了 Turbolinks,并且使用了更多的原生代码。这是原生 App 和 Web App 的一次更加深入的融合。

Hybrid 的定义

首先,我们需要清楚理解什么是”hybrid”。这个词在很多不同的场合中被使用,但这对于我们而言几乎没有任何意义。在我们的开发中,我们所指的”混合”是具有大量 web 渲染内容的标准原生应用。我们没有使用通过 HTML/CSS 来模拟原生控件的框架,也没有使用将另一种编程语言编译成原生开发语言的框架,亦或者是通过一份代码库开发一个跨平台的 App 的框架。

对于我们而言,这意味着我们需要使用 Xcode+Swift,并且要遵守开发平台关于导航和展示的所有规范。在 App 中构建的内容大多由 UINavigationControllerUIViewControllerUITabViewControllerUISplitViewController 等组成。在这些容器中,有很多内容是通过 UITableViewUICollectionView 组建的,当然了更多的是通过 WKWebView。

深入理解

在 iOS 版本的 Basecamp 3 App 中我们使用的全部是 Swift 3.1,并且通过最新版本的 Xcode 进行编译。我们只有很少的依赖,并且这些都是通过 Carthage 进行管理。Turbolinks 是我们能够使用混合架构的核心库。Turbolinks 不仅可以用在 Web 上,在 iOS 和 Android 的原生应用上也可以进行使用。此框架主要解决了原生应用和 Turbolinks.js 之间的通讯问题,并且允许开发者在多个界面间共享单个 WKWebView。

路由/导航

除了 Turbolinks,我们还需要许多其他的组建进行支持。在 iOS 应用中大多数都是通过 URL 进行的导航。一个 URL 可以有很多来源(web 链接、推送通知、来自另一个 App 的通用链接、本地跳转等),但是它们都要通过路由进行中转。路由需要清楚地知道对于一个给定的 URL 下一步操作是什么。如果 URL 是另一个域,应用还可能打开 Safari;如果是图像/视频,则需要展示一个媒体视图,又或者是在大多数情况下,应用都是创建一个新的控制器进行展示。大多数的控制器都需要被推入当前导航控制器的栈中,但是我们也支持通过模态的方式弹出一个视图(类似新的/编辑视图) 以及在合适的时候替换当前视图。

桥接

组成混合架构的最后一个组件是桥接(尽管我们有许多其他的组件,但它们都和混合模块没关系)。这是一个在 App 不同部分之间进行通讯的统称术语,比如原生->web通讯(或web->原生)。其中最核心的代码是一份嵌入到本地 App 的 JavaScript 文件(用 TypeScript 编写)并且通过 WKUserScript 注入到 web 视图中。这种桥接方式给原生代码提供了一个在不需要直接查询 DOM 和进行复杂 JS 操作的情况下与 web 视图进行通讯的 API。利用 WKScriptMessageHandle,我们可以通过桥接来响应从 Web 发送的消息。

从 iOS Basecamp 3 探索混合应用开发的最佳实践

以上是一个关于桥接的例子。我们使用桥接来隐藏一些在 web 上需要显示但在 Basecamp 界面中不需要的元素。由于我们给顶层导航控制器提供了一个标签条,所以我们需要隐藏 web 界面最下面的部分。同时,也不需要 web 的导航记录了因为我们已经有了一个导航控制器。最后,我们隐藏了 Web 的编辑/书签/动作菜单并且提供了一个原生版本的界面。

案例

通过下面几个例子我们将在实际中更加容易感受到上面所说的意思。在下面的图片中,我将用一个紫色的叠加层来标识 web 视图,一个绿色的叠加层来标识原生 UI。

主标签

iOS 版本的 Basecamp 3 有四个主标签(Home、Hey!、Activity 和 Find)。这些标签每一个都是 100% 原生的。由于这些标签是 App 中主要的交互点,因此我们希望它们能够尽可能的快速响应用户交互。另外我们也十分想提供一个与桌面应用不同的体验,我们认为这个在移动端上更加具有意义。比如 Hey 标签和最近的所有通知。

从 iOS Basecamp 3 探索混合应用开发的最佳实践

消息

当你点击 Hey 标签的通知,想要获取一个新消息时,我们将在导航控制器的栈中推入一个 TurbolinksViewController 控制器。

从 iOS Basecamp 3 探索混合应用开发的最佳实践

这是一个典型的所有内容都是 web 的视图。我们在页面中通过桥接方式取出数据并展示在导航条上。同样地,当你点击按钮的时候,我们将从 DOM 中取出数据并填充一个原生的弹出菜单。由于在这种混合架构中都是由界面提供的动态性数据,于是我们可以在服务端随时更改数。最终,当你点击导航条标题时,我们将展示一个原生的工具菜单,给用户提供项目导航的快速访问功能。

Campfire

我们也有一些由原生和 web 混合的视图。下面是 Campfire 的一个例子:

从 iOS Basecamp 3 探索混合应用开发的最佳实践

主要的聊天界面是 web 构成的,但是我们决定使用原生视图进行输入。这样可以修复一些由 web 输入造成的问题,比如滚动时保持正确的位置,同时也可以更好地控制交互式键盘的消失。当输入某人名字的时候,我们可以使用一个原生的自动补全器。点击回形针按钮可以显示一个原生的附件选择器,这使我们在整个应用中使用了一些很好的触摸,比如快速挑选醉经拍摄的照片。这些组件可以在同一个视图中无缝衔接。

总结

下面是几个例子演示了这种方式的灵活性。这种架构的关键是我们没有被限制在一种方式或者框架中。Native 和 Web 并不是非此即彼的选择,而是一种类似光谱的范围选择。

从 iOS Basecamp 3 探索混合应用开发的最佳实践

对于 App 的每一个视图,我们都可以调整 Web 和原生开发的比例。我们认为,如果一个原生视图很少被使用并且不值得维护,我们会将它改成 Web 形式。如果一个 web 视图没有提供一个最好的用户体验,我们将用原生替待。我们可以尝试 React Native 或者将其混合使用。无论何时 Apple 发布新的 API,我们都可以立即对其支持,因为我们不依赖第三方框架进行更新。

在 Basecamp 中我们非常看重的一件事就是团队的独立性。如果我们必须协调 web、iOS 和 Android 开发团队中每一个新特性的开发和发布工作,我们将永远不能够前进。这种架构模式允许 web 开发团队构建一个新特性并且在所有平台上同时发布。在 Basecamp 3 中我们默认有一个展示任何 URL 的视图控制,所以在 App 中任何新的 URL 可以正常工作。我们可以在 web 上迭代和实验,立即在全平台发布,之后如果我们觉得可以提升它的用户体验,我们会用原生替代它。

这也使我们的移动团队专注于如何最好的服务平台。我们的目标之一是在手机应用中覆盖 100%,因为应用不支持某些功能,因此您不必进入桌面。 凭借网络提供的坚实基础,我们可以实现这一目标,然后将重点放在平台特定的改进上。 这些功能包括丰富的内容推送通知,通用链接,切换支持,iCloud Keychain 支持,共享扩展,今天的小部件等功能。 如果我们没有完全的本地支持,那些这些事情将是不可能的或不平凡的。如果没有 100% 原生的支持, 这些改进中的部分功能将不可能实现或者变得卓越。

从 iOS Basecamp 3 探索混合应用开发的最佳实践