随着系统的复杂,把功能进行细化,把整合 View 展示数据的逻辑的独立出来形成 ViewModel 模块,架构风格就变成了 MVVM:
(图片来源)
随着系统的更加复杂,把路由的职责,获取数据的职责也独立出去,架构风格就变成了 VIPER:
(图片来源)
本文则想从另一个角度和大家探讨一个新的 iOS 应用架构方案,架构的本质是管理复杂性,在讨论具体的架构方案前,我们首先应该明确一个 iOS 应用的开发,其复杂性在哪里?
对于一个 iOS 应用来说,其开发的复杂性主要体现在三个方面:
iOS App 最终呈现给用户的是一组组的 UI 界面,而对于一个特定的 App 来说,其 UI 的设计元素(如配色,字体大小,间距等)基本上是固定的,另外,组成该 App 的基础组件(如 Button 种类,输入框种类等)也是有限的。但是如何管理,组合,重用组件则是架构师需要考虑的问题,尤其是一些 App 在开发过程中可能出现大量的 UI 样式重构,更需要清晰的控制住重构的影响范围。这儿的复杂性本质上是 UI 组件自身设计实现的复杂性,多 UI 组件之间的组合方式和 UI 组件的重用机制。
对于一个大型的 iOS 应用,通常会把其功能按 Feature 拆分,经过这样的拆分之后,其可能出现的路由有以下几种:
根据 Apple 官方的 MVC 架构,这些复杂的各种跳转逻辑,以及跳转前的 ViewController 的准备工作等逻辑缠绕在 AppDelegate 的初始化,ViewController 的 UI 逻辑中。这儿的复杂性主要是 UI 和业务之间缠绕不清的相互耦合。
一个 iOS 应用本质上就是一个状态机,从一个状态的 UI 由 User Action 或者 API 调用返回的 Data Action 触发达到下一个状态的 UI。为了准确的控制应用功能,开发者需要能够清楚的知道:
在 MVC,MVVM,VIPER 的架构中,应用的状态分散在 Model 或者 Entity 中,甚至有些状态直接保存在 View Controller 中,在跟踪状态时经常需要跨越多个 Model,很难获取到一个全貌的应用状态。另外,对于 Action 会如何影响应用的状态跟踪起来也比较困难,尤其是当一个 Action 产生的影响路径不同,或最终可能导致多个 Model 的状态发生改变时。这儿的复杂性主要体现在治理分散的状态,以及管理不统一的状态改变机制带来的复杂性。
前面明确了 iOS 应用开发的复杂性所在,那么从架构层面上应该如何去管理这些复杂性呢
UI 界面的复杂度本质上是一个点上的复杂度,其复杂性集中在系统的某些小细节处,不会增加系统整体规划的复杂度,所以控制其复杂度的主要方式是隔离,避免一个 UI 组件之间的相互交织,变成一个面上的复杂度,导致复杂度不可控。在 UI 层,最流行的隔离方式就是组件化,在笔者之前的一篇文章《前端组件化方案》中详细解释了前端组件化方案的实施细节,这儿就不再赘述。
应用的路由主要分为 App 间路由和 App 内路由,对它们需要分别处理
对于 APP 之间的路由,主要通过两种方式实现:
一种是 URL Scheme 通过在当前 App 中配置进行相应的设置,即可从别的 APP 跳转到当前 APP。进入当前 App 之后,直接在 AppDelegate 中的方法:
- func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool
转换进 App 内的路由。
另一种是 Universal Links,同样的通过在当前 App 中进行配置,当用户点击 URL 就会跳转到当前的 App 里。进入当前 APP 之后,直接在 AppDelegate 中的方法:
- func application(_ application: UIApplication,
- continue userActivity: NSUserActivity, restorationHandler: @escaping([Any] ? ) - >Void) - >Bool
中转进 App 内路由。
所以 App 间的路由逻辑相对简单,就是一个把外部 URL 映射到内部路由中。这部分只需要增加一个 URL Scheme 或 Universal Link 对应到 App 内路由的处理逻辑即可。
对于内部路由,我们可以引入 App Coordinator 来管理所有路由。App Coordinator 是 Soroush Khanlou 在 2015 年的 NSSpain 演讲上提出的一个模式,其本质上是 Martin Fowler 在《Patterns of Enterprise Application Architecture》中描述的 Application Controller 模式在 iOS 开发上的应用。其核心理念如下:
经过这层抽象之后,一个复杂 App 的路由对应关系就会如下:
从图中可以看出,应用的 UI 和业务逻辑被清晰的拆分开,各自有了自己清晰的职责。ViewController 的初始化,ViewController 之间的链接逻辑全部都转移到 App Coordinator 的体系中去了,ViewController 则彻底变成了一个个独立的个体,其只负责:
通过引入 AppCoordinator 之后,UI 和业务逻辑被拆分开,各自处理自己负责的逻辑。在 iOS 应用中,路由的底层实现还是 UINavigationController 提供的 present,push,pop 等函数,在其之上,iOS 社区出了各种封装库来更好的封装 ViewController 之间的跳转接口,如 JLRoutes,routable-ios,MGJRouter 等,在这个基础上我们来进一步思考 App Coordinator,其概念核心是把 ViewController 跳转和业务逻辑一起抽象为 user intents(用户意图),对于开发者具体使用什么样的方式实现的跳转逻辑并没有限制,而路由的实现方式在一个应用中的影响范围非常广,切换路由的实现方式基本上就是一次全 App 的重构(做过 React 应用的 react-router0.13 升级的朋友应该深有体会)。所以在 App Coordinator 的基础之上,还可以引入 Protocol-Oriented Programming 的概念,在 App Coordinator 的具体实现和 ViewController 之间抽象一层 Protocols,把 UI 和业务逻辑的实现彻底抽离开。经过这层抽象之后,路由关系变化如下:
经过 App Coordinator 统一处理路由之后,App 可以得到如下好处:
前面提到引入 App Coordinator 之后,ViewController 的剩下的职责之一就是 "接收数据并把数据绑定到对应的子 UIView 展示",这儿的数据来源就是应用的状态。它山之石,可以攻玉,不只是 iOS 应用有复杂状态管理的问题,在越来越多的逻辑往前端迁移的时代,所有的前端都面临着类似的问题,而目前 web 前端最火的 Redux 就是为了解决这个问题诞生的状态管理机制,而 ReSwift 则把这套机制带入了 iOS 的世界。这套机制中主要有一下几个概念:
在这个机制下, 一个 App 的状态转换如下:
在这个状态转换的过程中,需要注意,业务操作会有两类:
整个 App 的状态变换过程如下:
无异步调用操作的状态流转(图片来源)
有异步调用操作的状态流转(图片来源)
经过 ReSwift 统一管理应用状态之后,App 开发可以得到如下好处:
经过上面的大篇幅介绍,下面我们就来归纳下结合了 App Coordinator 和 ReSwift 的一个 iOS App 的整体架构图:
上面已经讲解了整体的架构原理,"Talk is cheap", 接下来就以 Raywendlich 上面的这个 App 为例来看看如何实践这个架构。
来源: http://www.infoq.com/cn/articles/ios-arch-based-on-reswift-and-app-coordinator