自2014年起,移动端的架构中开始渐渐的融入了响应式的元素。时至今日,融入响应式的架构已经在很多产品的多次迭代中经受住了考验。十年后,再回首现在这一时间点,很可能会被认为是一个新的移动端架构时代的开端。很多文章中都提出了针对自己产品研究出来的一套架构,却鲜有文章单独对架构中的响应式元素进行讨论。笔者就写下了这样一篇关于iOS响应式架构的文章来阐述响应式架构的历史、响应式架构的本质、响应式架构的优点以及如何在现有架构中融入响应式架构这几个值得探讨的问题。

First Things First

首先,重中之重。界定一下响应式架构与响应式编程和其他架构的边界。响应式架构与FRP(响应式编程)有必然联系吗?没有的,响应式架构并不需要引入Rx等FRP范式方案。响应式架构与MVC、MVVM有必然联系吗?没有的,MVC中可以很愉快的融入响应式架构,MVVM也可以。不过,响应式架构与Immutability倒是密不可分,毕竟不可变才能“响应”。

Immutability

2014年Facebook开源了immutable.jsReactFlux等以immutability作为核心元素的方案,可以说是席卷了前端。这看上去是在前端的浪潮之中,Facebook提出了这一概念。事实并非如此,Immutability的概念早在面向对象编程诞生的阶段就已经有了。而Facebook的工程师是这个时代发现珍珠的人。

这里也不打算长篇大论,简要的回答这几个问题。不可变性是啥?可变数据与不可变数据有什么区别?

不可变性就是不可变数据具有的不可变性质,而可变数据和不可变数据的区分就在是否具有状态,可变数据具有,而不可变数据不具有。首先来思考一下这个有趣的问题,现实世界中有钟表反映时间的流逝,那在计算机世界中如何反映时间的流逝?同一数据发生了变化就可以,比如说,计时器的本质不就是同一数据一直在递增,即状态反映了时间的流逝。其实,状态是对现实世界时间流逝的隐式表现。引入了状态,也就向计算机世界引入了时间。

那我们为什么要关心状态,状态有什么危害吗?数据本身改变其状态实际上没什么危害,而且是必需的,任何对本身状态的修改都是可预测的。而多数据共享的状态则不同,任何一方的修改对其他方来说都是不可预测的。这也就是平时编程bug的源泉。(可变数据带来的同一性问题)

除此了共享状态,状态带来的时间,也同样带来了语句的时序问题,CPU流水线执行的读写状态的语句就变得至关重要,影响了程序的正确性。其中最令人头痛的就是线程安全问题。

Immutable Model Layer in iOS

状态带来这两点危害,在GUI编程中尤其显著。为了缩短平时与bug争斗的时间,前端和客户端的工程师们开始了Immutable之旅。然而从编程的模型中剥离状态是不现实的,那么就从架构的一个层面上剥离状态吧,很显然,Model赢得了头奖。

FacebookPinterestlinkedin很早就有文章说明他们在这方面的尝试,并都对外输出了自己的开源方案。Facebook开源了remodel,linkedin开源了Consistency-Manager,Pinterest开源了Plank。而大家管App上剥离状态的这种设计叫做Immutability Model Layer。Immutability Model Layer不是一步登天的,Facebook在其文章中分享了这段心路历程。

首先,在日常业务编写Model的时候,工作无趣而且繁重,很多工作量就是将后端提供的API转换成Model,根本不需要动脑进行抽象,这是个要解决的问题。

其次,在平时面向对象编码过程中很容易将多个对象共享的状态混在其中一个对象的行为里更改。比如说,多个对象的共享状态,在其中任何一个对象的方法里更改。实际上,应该再单独抽象一个对象(single model)去做对共享状态的修改,这使共享状态的代码隔离出来,提高了维护性,也使共享状态的修改更加可预测。

这种方法确实会减少bug,让代码更加可预测,然而每次要警觉到这一点再创建个新的single model听上去就是个无法“实施”的方案。唯有继续上下而求索,后来摸索出的方案就是本文的主题,直接让Model Immutable化。将Model设计成与其他Immutable数据NSArray、NSDictionary一致。Model生成后不可以修改其内容,如果要修改再重生成一个新的Model。

Facebook和Pinterest提出的方案都是将Model的属性声明为readonly。并用Builder设计模式,抽象一个Model Builder,Model Builder通过原Model生成,生成后,Model Builder可以自由修改数据,然后再由Model Builder生成修改后的新Model。

本来编写Model就是比较枯燥的工作,如果再将Model Immutable化,要花更多的时间写样板化(boilerplate)代码,更加加重了平时编写Model的负担。为了将Model Immutable化还不给平时工作加重负担,remodel就横空出世了。

Flux

要想理解响应式架构的本质,还是要把Facebook提出的响应式架构元老Flux请出来。听说过Flux的都知道,Flux有三个重要部件,view、dispatcher、store,并且Flux是单向data flow。

flux

  • view:类似于controller-views,将data存储、变动隔离出去。view只能引起数据变动和等待data变动的通知。view这一部分更适合用React(Immutable化的视图)。
  • store:明确抽象data状态变动部件,由于data存储经常是data状态变动的副作用,所以将data存储和data状态变动共同抽象为一个部件。只有store中的data是可变的,其他部件中的data都是不可变的,状态变动限制在局部。
  • dispatcher:明确抽象调度者部件,降低多方数据源依赖时和频繁失效式(cascading updates)data变动带来的复杂度。
  • action:由于其他部件不能更改data,若想更改data必须提出申请,抽象更改data单位,提交给dispatcher处理。

任何一种架构,都是用合适的抽象降低复杂度,Flux也不例外。Flux主要降低的是以下三点:

  • 将不定的data flow简化为固定的data flow,固定的data flow主要指的是必须通过固定的接口,经过固定的部件,并使每条data flow清晰、易于人脑理解,打log、debug,更重要的是这让data flow可预测。
  • 将data存储与其状态变动明确抽象为一个部件,并把状态改变限制在局部。
  • 明确抽象调度者,降低多方数据源依赖时和频繁失效式状态更新带来的复杂度。

注意:Flux虽然有如此多的好处,但是Flux架构的前题是App足够复杂,这个足够复杂的标志是一个Controller会对应多个View、Model,如果想从中更改或者添加一个View、Model,会怕破坏现有稳定性而无从下手。并且解决这种复杂并不是只有Flux这一种解决方式。

Flux将data的状态变动从view中隔离出去,view同样也Immutable化了?实际上,还没有,因为不管是浏览器,还是iOS、Android,view都是有状态的。这就轮到React出场了,iOS中则是ComponentKit作为花旦,当view有状态变动,重新生成view。如果还按照以往的render逻辑,很明显会产生性能问题。所以,React就会对view层进行重构,将每个view抽象为一个component,component又嵌套component,模拟树的层级结构,抽象的粒度尽量小,当view的状态变动,React将diff出所有变动的地方,然后重新生成涉及到的component。

本质

那什么是响应式架构的本质?这里就下个定义吧,并不是通过工具生成Immutable化Model,也不是固定的几个抽象部件,而是将软件的视图当做data状态的一个响应呈现。

响应式架构有以下三个明显优势:

  • 固定data flow,易于人脑理解,也让data flow更可预测。
  • 从架构层面上消灭共享状态,让多方共享的数据可预测。
  • 共享数据线程安全。

那如果不用响应式架构,可不可以同样的学习其思想?我们可以尽量写出更可预测的代码,比如Swift对于Optional的引入,就是让可能为nil更可预测。

融入现有架构

首先,你要足够了解产品现有的架构。其次,请尽量融入,而不“重造”。融入响应式架构不只有一种方式,尽量选择较温和的那一款。

  • 使用或开发一套自动生成Model的工具,将Model Immutable化。通过观察者设计模式,在需要得知Model改变的地方注册观察者。任何Model的修改,无论是网络请求、交互等原因,都新生成一个Model并把Model push出去,具体设计可以参考Consistency-Manager。业务编码要在controller-views里observe,在数据层接口获取Model和交互改变Model的地方push Model。
  • 引入Rx等FRP范式方案,结合MVVM,具体设计参考limboy的这篇文章

无论如何选择,请一定要参考产品的复杂度和已有架构。毕竟,重要的是写出更可预测的代码,形式只是表达。而以笔者业务开发的经历,自动生成Model是很有必要的,自动生成的代码,更少的错误、更统一的规范,这都是我们写代码的崇高追求。不过,国内外开发环境不一样,国外开发的工具确实不能生搬硬套到国内,这就是另外一个话题了,后期有机会笔者会开源正在使用的自动代码生成工具。

除了将Model Immutable化,那View要Immutable化吗?这是一个值得好好权衡的问题,硅谷的科技公司对View的Immutable化做的很彻底,从GMTC大会上Airbnb分享的代码实践里将View起名为Component就能猜测出一二了。但是国内确实是不温不火,究其原因还是学习成本成了拦路虎,国内公司的迭代速度是硅谷无法匹敌的,在快速的试错、迭代中,View层的变化始料不及,公司对技术的要求必然是可让新手快速上手、迭代,将View Immutable化,要整体重构系统View层,对于没有接触过的新手很不友好。如果让新人入职第二天开始写需求,而他都没法在新项目中好好写个View,这就是不可接受的了。

说到底,什么应用适合响应式架构呢?新闻类和信息展示类应用会很适合响应式架构,换句话说,也就是大部分App都适合。