iOS App Basecamp 3: 混合架构
以前, 我们已经写了很多篇关于如何构建混合移动应用方法的文章. Basecamp3 项目代表了混合移动架构的最新版本, 并且它从过去的版本中吸收了很多精华.
Basecamp 2项目的第一个App仅仅适用于iPhone, 它是利用RubyMotion作为UIWebView的封装器来进行编写的. 之后, 我们又通过Xcode+Objective-C, UIWebView以及更多原生代码的方式去构建一个通用型的App. 对于Basecamp 3, 我们使用Swift和WKWebView代替Objective-C和UIWebView, 新增了Turbolinks, 并且使用了更多的原生代码. 这是原生App和web App的一次更加深入的融合.
混合的定义
首先, 我们需要清楚理解什么是”混合”. 这个词在很多不同的场合中被使用, 但这对于我们而言几乎没有任何意义. 在我们的开发中, 我们所指的”混合”是具有大量web渲染内容的标准原生应用. 在这个定义中, 我明确地指出内容是因为这是一个非常重要的区别. 我们没有使用通过HTML/CSS来模拟原生控件的框架, 也没有使用将另一种编程语言编译成原生开发语言的框架, 亦或者是通过一份代码库开发一个跨平台的App的框架.
对于我们而言, 这意味着我们需要使用Xcode+Swift, 并且要遵守开发平台关于导航和展示的所有规范. 在App中构建的内容大多由UINavigationController, UIViewController, UITabViewController, UISplitViewController等组成. 在这些容器中, 有很多内容是通过UITableView或UICollectionView组建的, 当然了更多的是通过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. 利用WKScriptMessageHandler, 我们可以通过桥接来响应从Web发送的消息.
以上是一个关于桥接的例子. 我们使用桥接来隐藏一些在web上需要显示但在Basecamp界面中不需要的元素. 由于我们给顶层导航控制器提供了一个标签条, 所以我们需要隐藏web界面最下面的部分. 同时, 也不需要web的导航记录了因为我们已经有了一个导航控制器. 最后, 我们隐藏了Web的编辑/书签/动作菜单并且提供了一个原生版本的界面.
案例
通过下面几个例子我们将在实际中更加容易感受到上面所说的意思. 在下面的图片中, 我将用一个紫色的叠加层来标示web视图, 一个绿色的叠加层来标示原生UI.
主标签
iOS版本的Basecamp 3有四个主标签(Home, Hey!, Activity, 和Find). 这些标签每一个都是100%原生的. 由于这些标签是App中主要的交互点, 因此我们希望它们能够尽可能的快速响应用户交互. 另外我们也十分想提供一个与桌面应用不同的体验, 我们认为这个在移动端上更加具有意义. 比如Hey标签和最近的所有通知.
消息
当你点击Hey标签的通知, 想要获取一个新消息时, 我们将在导航控制器的栈中推入一个TurbolinksViewController控制器.
这是一个典型的所有内容都是web的视图. 我们在页面中通过桥接方式取出数据并展示在导航条上. 同样地, 当你点击按钮的时候, 我们将从DOM中取出数据并填充一个原生的弹出菜单. 由于在这种混合架构中都是由界面提供的动态性数据, 于是我们可以在服务端随时更改数据. 最终, 当你点击导航条标题时, 我们将展示一个原生的工具菜单, 给用户提供项目导航的快速访问功能.
在线交流系统营火
我们也有一些由原生和web混合的视图. 下面是Campfire的一个例子:
主要的聊天界面是web构成的, 但是我们决定使用原生视图进行输入. 这样可以修复一些由web输入造成的问题, 比如滚动时保持正确的位置, 同时也可以更好地控制交互式键盘的消失. 当输入某人名字的时候, 我们可以使用一个原生的自动补全器. 点击回形针按钮可以显示一个原生的附件选择器, 这使我们在整个应用中使用了一些很好的触摸, 比如快速挑选醉经拍摄的照片. 这些组件可以在同一个视图中无缝衔接.
总结
下面是几个例子演示了这种方式的灵活性. 这种架构的关键是我们没有被限制在一种方式或者框架中. Native和Web并不是非此即彼的选择, 而是一种类似光谱的范围选择.
对于App的每一个视图, 我们都可以调整Web和原生开发的比例. 我们认为, 如果一个原生视图很少被使用并且不值得维护, 我们会将它改成Web形式. 如果一个web视图没有提供一个最好的用户体验, 我们将用原生替待. 我们可以尝试React Native或者将其混合使用. 无论何时Apple发布新的API, 我们都可以立即对其支持, 因为我们不依赖第三方框架进行更新.
在Basecamp中我们非常看重的一件事就是团队的独立性。如果我们必须协调web, iOS和Android开发团队中每一个新特性的开发和发布工作, 我们将永远不能够前进. 这种架构模式允许web开发团队构建一个新特性并且在所有平台上同时发布. 在Basecamp 3中我们默认有一个展示任何URL的视图控制器, 所以在App中任何新的URL可以正常工作. 我们可以在web上迭代和实验, 立即在全平台发布, 之后如果我们觉得可以提升它的用户体验, 我们会用原生替代它.
这也使我们的移动团队专注于如何最好的服务平台。 我们的目标之一是在手机应用中覆盖100%,因为应用不支持某些功能,因此您不必进入桌面。 凭借网络提供的坚实基础,我们可以实现这一目标,然后将重点放在平台特定的改进上。 这些功能包括丰富的内容推送通知,通用链接,切换支持,iCloud Keychain支持,共享扩展,今天的小部件等功能。 如果我们没有完全的本地支持,那些这些事情将是不可能的或不平凡的。如果没有100%原生的支持, 这些改进中的部分功能将不可能实现或者变得卓越.