应用架构
我们通常使用简单的框图来解释app 的架构。比如,Apple 的MVC 模式可以通过model、 view 和controller 三层结构来描述。
在一个MVC 项目中,绝大部分的代码都会落到其中某个层上。箭头表示了这些层进行连接的方式。
但是,这种简单的框图几乎无法解释在实践中模式的操作方式。这是因为在实际的app 架构中, 部件的构建有非常多的可能性。事件流在层中穿梭的方式是什么?部件之间是否应该在编译期 间或者运行时持有对方?要怎么读取和修改不同部件中的数据?以及状态的变更应该以哪条路 径在app 中穿行?
Model 和View
Model 层和View 层是最为常⻅的两个分类。
-
Model 层是app 的内容,它不依赖于(像UIKit 那样的) 任何app 框架。也就是说,程序员对model 层有完全的控制。Model 层通常包括model 对象(在录音app 中的例子是文件夹和录音对象) 和协调对象(比如我们的app 例子中的负责在磁盘上存储数据的Store 类型)。被存储在磁盘上的那部分model 我们称之为文档model (documentation model)。
-
View 层是依赖于app 框架的部分,它使model 层可⻅,并允许用户进行交互,从而将model层转变为一个app。当创建iOS 应用时,view 层几乎总是直接使用UIKit。不过,我们也会看到在有些架构中,会使用UIKit 的封装来实现不同的view 层。另外,对一些其他的像是游戏这样的自定义应用,view 层可以不是UIKit 或者AppKit,它可能是SceneKit 或者OpenGL 的某种封装。
App 的本质是反馈回路
View 层和model 层需要交流。所以,两者之间需要存在连接。假设view 层和model 层是被清 晰地分开,而且不存在无法解耦的联结的话,两者之间的通讯就需要一些形式的翻译:
在model 层和view 层之间不同的路径拥有不同的名字。用户发起的事件会导致view 的响应, 我们把由此引起的代码路径称为view action,像是点击按钮或者选中table view 中的某一行 就属于view action。当一个view action 被送到model 层时,它会被转变为model action (或 者说,让model 对象执行一个action 或者进行更新的命令)。这种命令也被叫做一个消息(特别 在当model 是被reducer 改变时,我们会这么称呼它)。将view action 转变为model action 的操作,以及路径上的其他逻辑被叫做交互逻辑。
注:reducer 这个名字来自于函数式编程的reduce 操作,它指的是对一系列的值进行操作,把它们按照一定逻辑“减少” (合并) 到一个值中。一般来说,架构中的reducer 所负责的事情是,将一系列未来到来的变化(或者说消息),与当前的状态进行“合并”,并推导出新的状态。我们会在后面的章节,特别是TEA 的相关内容中,看到一个实际的经典reducer 的例子。
一个或者多个model 对象上状态的改变叫做model 变更。Model 的变更通常会触发一个 model 通知,比如说从model 层发出一个可观测的通知,它描述model 层中什么内容发生了 改变。当view 依赖于model 数据时,通知会触发一个view 变更,来更改view 层中的内容。 这些通知可以以多种形式存在:Foundation 中的Notifcation,代理,回调,或者是其他机制, 都是可以的。将model 通知和数据转变为view 更改的操作,以及路径上的其他逻辑被叫做表 现逻辑。
根据app 模式的不同,有些状态可能是在文档model 之外进行维护的,这样一来,更新这些状 态的行为就不会追随文档model 的路径。在很多模式中的导航状态就是这种行为的一个常⻅例 子,在view 层级中的某个部分(或者按照Cocoa Storyboard 中使用的术语,将它称为scene) 可能会被换出或者换入层级中。
在app 中非文档model 的状态被叫做view state。在Cocoa 里,大部分view 对象都管理着它 们自己的view state,controller 对象则管理剩余的view state。在Cocoa view state 的框图 中,通常会有加在反馈回路上的捷径,或者单个层自身进行循环。在有一些架构中,view state 不属于controller 层,而是属于model 层的部分(不过,根据定义,view controller 并不是文 档model 的一部分)。
当所有的状态都在model 层中被维护,而且所有的变更都通过完整的反馈回路路径进行传递 时,我们就将它称为单向数据流。当任意的view 对象或者中间层对象只能够通过model 发出 的通知来进行创建和更新(换句话说,view 或者中间层不能通过捷径来更新自身或者其他的 view) 时,这个模式通常就是单向的。
App 任务
要让程序正常工作,view 必须依赖于model 数据来生成和存在,我们配置view,让它可以对 model 进行更改,并且能在model 更新时也得到更新。
app 中如何执行下列任务:
-
构建— 谁负责构建model 和view,以及将两者连接起来?
-
更新model — 如何处理view action?
-
改变view — 如何将model 的数据应用到view 上去?
-
view state — 如何处理导航和其他一些model state 以外的状态?
-
测试— 为了达到一定程度的测试覆盖,要采取怎样的测试策略?