基于彻底解耦合的实验性iOS架构

513 查看

这周我决定做一个关于彻底解耦合的应用架构的实验。我想探究的主题是:

“如果所有的应用内通讯都通过一个事件流来完成会怎么样?”

我构造了一个待办事项应用,因为这是我一时激动下所能想到的最原始微型的项目。我会大概地说一下应用结构背后的想法,展示具体实现中的一些代码片段,然后给出几个有关利弊的结论。

整个项目在Github上。作为参考,这篇文章基于0.1标签下的代码。

event-mvvm-demo.gif

应用演示

架构总述

为了有一个名字来关联,我把这个架构叫做EventMVVM。它使用少量的MVVM(Model-View-ViewModel)架构。虽然它使用ReactiveCocoa作为事件流的管道,但是我在后面会说到许多工具也可以代替。它是用Swift写的,这有点重要,由于Swift的枚举关联值的特性以及容易定义和使用值类型。

我能够解释架构的最好方法是命名和列举参与者,定义它们,再列出规则。

  • Event
  • EventsSignal & EventsObserver
  • Server
  • Model
  • ViewModel
  • View

Event

一个Event是一个消息的构建代码块。定义为枚举,每种情况下都有多达一个相关联的值(注意:这与ReactiveCocoa的Event不同)。你可以把它看作一个强类型NSNotification。每一种情况约定以Request或Response开始。下面是几个例子。

  • Model和ViewModel”类型”的事件都包括在Event枚举中(注解:1)。
  • RequestReadTodos没有参数,因为这个应用不需要预先筛选或排序注解:2
  • 我们使用Result来封装返回值或异常注:3
  • 所有枚举项的关联值都是值类型,这对于确保系统的健全是很重要的。同一个Event可能被任何一个的线程上的许多对象接收到。

EventsSignal & EventsObserver

eventsSignal和eventsObserver将是我们共享的事件流。我们将把它们注入进类里,这些类将能够附加观察者块到eventsSignal,并发送新的Event到eventsObserver。

我们把这个元组放在一个叫做AppContext的类里。它们使用一个ReactiveCocoa的Signal和一对通过.pipe()创建的观察者来实现。这里有一些实现细节,稍后我们将讨论。

简而言之语法如下:

Server

Server是一个长久存活的类,它包含观察者并能发送消息。在我们的示例应用中,有两个Server–ViewModelServerhe和ModelServer。这些都是由AppDelegate创建并持有的。从名字你可能会认为ViewModelServer设置了我们应用的ViewModel相关的职责的观察者。例如,它负责为ViewModels接收请求并满足它们,不是改变事件里的ViewModel,就是发送一个新的事件请求它需要的数据(注解:4注解:5)。

Server代表我们应用里的”智能”对象。它们是协调器。它们创建和操纵我们的ViewModel、Model值类型,并与其他server通过创建Event和附加在它们之上的值进行交流。

Model

一个Model是一个包含基本数据的值类型。在标准MVVM里,它不应该包含任何一个针对底层数据库的东西。

在示例应用中,我用扩展来把Todo model对象序列化成TodoObject用于我们的Realm数据库。

模型层只知道自己。它不知道ViewModel和View。

ViewModel

一个ViewModel是一个值类型,它包含在View层里并且是一个可以直接使用的属性。例如,UILabel显示的文本就该是一个String。ViewModel在init函数里接收和存储一个Model对象,并将之转变为View层可使用的。一个ViewModel可使其他ViewModels能够被子视图等使用。

按这种解释(注解:6),ViewModels是完全惰性的,并且不能异步操作和向事件流发送消息。这确保它们可以安全地在线程间传递。

ViewModel不知道View层。它们可以操作其他ViewModel和Model。

View

我们的View层是UIKit,包括UIViewControllers和UIViews及其子类。虽然我的初衷是探索让View层也通过事件流发送自己的事件,但是在这个简单的实现里却是不必要的,并且可能是最使人分心的(注解:7)。

View层只允许与View和ViewModel层进行交互。这意味着它对Model一无所知。

实现

现在我们对所有的组件系统已经有了一个基本的了解,让我们深入进代码,看看它是如何工作的。

The Spec(软件规格说明书)

我们的待办列表的特点是什么?这类似于我们的Event。(对我来说,这是最激动人心的部分。)Event.swift:

  • RequestTodoViewModels:我们希望能够看到所有待办事项按预设顺序排序,并过滤掉已删除的条目。
  • RequestToggleCompleteTodoViewModel:我们需要能够在列表视图把待办事项标记为完成。
  • RequestDeleteTodoViewModel:我们也需要能够将在列表视图删除它们。
  • RequestNewTodoDetailViewModel:我们需要能够创建新的待办事项。
  • RequestTodoDetailViewModel:我们需要能够漂亮地查看/编辑一个待办事项。
  • RequestUpdateDetailViewModel:我们需要能够提交我们的更改。

这些都是我们的请求。它们将所有来自View层。因为这些只是我们广播的事件/消息,不一定有直接的一对一的响应。这对我们同时有积极和消极的后果。

影响之一是我们需要更少类的响应事件。ResponseTodoViewModels和RequestTodoViewModels会有一对一的响应,但RequestToggleCompleteTodoViewModel、RequestDeleteTodoViewModel和RequestUpdateDetailViewModel都会由ResponseTodoViewModel响应。这简化了我们的view的代码,也保证了一个view可以获得更新并传给被一个不同的view改变的ViewModel,我们也不需要额外做什么。

RequestNewTodoDetailViewModel和RequestTodoDetailViewModel(又名新建和编辑)将由ResponseTodoDetailViewModel响应。

有趣的是,RequestUpdateDetailViewModel必须由ResponseUpdateDetailViewModel和ResponseTodoViewModel响应,因为它们的底层待办Model改变了。稍后我们将详细探讨这个场景。

为了满足这些来自View层的请求,ViewModelServer需要有自己的对Model数据的请求。这些都是一对一的请求-响应。

  • RequestReadTodos -> ResponseTodos
  • RequestWriteTodo -> ResponseTodo

我们在待办Model里通过设置一个flag来实现删除。这种技术明显使它能更容易地协调我们的应用层之间变化。

以下是一个很长的图,有关这四个主要对象如何发送和观察事件。

event-mvvm-diagram.jpg

系统设置