专栏名称: 知识小集
目录
相关文章推荐
曲线猎手  ·  2025年融资融券的最低利率【最低3.0%】 ·  昨天  
壹股经  ·  调整中寻机会! ·  昨天  
壹股经  ·  调整中寻机会! ·  昨天  
银行家杂志  ·  中国外贸信托卫濛濛:提升信托服务质效 ... ·  4 天前  
淘股吧  ·  DeepSeek,大消息! ·  3 天前  
淘股吧  ·  牛市二波!又要吹爆了...... ·  3 天前  
51好读  ›  专栏  ›  知识小集

Minya 分层框架实现的思考(一):依赖转移

知识小集  · 掘金  ·  · 2018-04-13 04:40

正文

Minya 分层框架实现的思考(一):依赖转移

Minya 框架是我们团队之前构建的一套分层框架,在一个内部项目上验证了一下。 Minya 只是项目名称,取自贡嘎雪山的英文名。这套分层框架是在 MVCS 的基础上进行了改造。后来团队转向 RAC + MVVM ,就搁置起来,没有再用。之前一直想整理一下,将一些基础的思想写出来,供大家参考。然后就一直拖到现在。我后期也反思过,这个框架还存在不少问题,也会在这个系列里也会详细说明。

Minya 之前已开源,并放到 Github https://github.com/southpeak/Minya 上,有兴趣的话可以给个 Star 。不过项目已停更,后期不再维护。

我们在考量一个 App 分层框架的设计和使用时,主要会基于以下几个原则:

  • 低耦合;
  • 单一职责,各层各司其职;
  • 可复用;
  • 结构清晰、代码均分;

另外,我们程序的运行主要是对数据做各种各样的操作,例如从服务端获取数据显示给用户,或者从用户输入中收集数据发送到服务端。因此我们可以从数据的角度来考虑整个框架的设计。

数据的流向有两个维度: 纵向 横向 。纵向是从垂直分层的角度来考虑,数据在 View 服务端/本地存储 之间传输;横向是从水平角度来考虑,数据从一个 ViewController 流向另一个 ViewController ,或者从一个模块流向另一个模块。这一系列文章将主要从纵向数据流这个维度来考虑分层框架的设计。同时将一步步绘制我们的框架图,并结合代码,来说明 Minya 框架的设计、使用和存在的问题。整个系列基本分为三个部分:

  • 转移依赖
  • 构建依赖和数据通信
  • 问题

如果有闲情,可以赖着性子慢慢看。

MVCS

传统的 MVC 可以满足 App 开发的基本需求,但在实际的开发中,经常因为各种原因,导致 ViewController 层的代码过于庞杂,同时各层的职责不明确,最终影响到项目的维护。这也是业界讨论的一个热点:如何轻量化 ViewController 。为此衍生出各种实用的框架,现在流行度比较高的应该是 MVVM 。我们在最初考虑的时候用了 MVCS 框架,主要目的有以下几个方面:

  • 明确每一层的职责;
  • 轻量化 ViewController ,让其成为数据的中转站;
  • 将业务处理独立于一层,并可根据实际情况将业务细分到不同的类中进行处理;
  • 均分代码,确保一个类或文件的代码量不会太大。

这一框架基本结构图及依赖关系如下:

图1:MVCS 基本结构图

对于每一层的职责,我们的基本考虑是:

  • Model 层:主要是用于表示数据,可以将其视为数据载体。在这一层中不做具体的业务处理,但可以执行一些简单的数据处理任务。即为 瘦Model 模型。在上图中没有表示出来;
  • View 层:即展示层,主要是将数据展现给用户,同时从用户输入中获取数据;
  • ViewController 层:该层将只作为 View 层的容器,以及数据的中转结点。这种中转包括纵向数据和横向数据的传输;
  • Store 层:业务处理层,所有的业务在这一层中来处理,这一层将只提供接口供上一层使用;
  • 数据访问层 :这一层主要面向服务端和本地数据,即图中的 Service Storage ,这一层不是我们的重点,在文中没有过多的讨论;

我们将下面三个部件统一为 Store ,即 MVCS 中的 S 实际上包含了三个主要部件: Store Service Storage

职责明确好了,我们就需要考虑数据如何在各层之间传输了。数据在对象之间的传输主要有几种方式:

  • 方法调用
  • KVO
  • Delegate
  • Notification
  • callback

如果在图1的基础上加上数据传输方式,结构图看起来可能是下面这样的(注意箭头方向):

图2:数据传输方式

图中数据的传输,从上到下是 View 层通过 Delegate 传递 ViewController ViewController 再通过方法调用将传递给 Store 做业务处理;从下到上是 Store 通过 callback 将数据回传给 ViewController ViewController 再通过方法调用传递给 View

这里有一些痛点:

  1. ViewController View Store 是强依赖,需要知道两者的很多信息。如果处理不好, ViewController 所需要做的工作依然很多;
  2. ViewController 作为 View 层的 Delegate ,实际上是让 View 层也依赖于 ViewController
  3. View 层如果视图层级很深,需要通过层层代理将数据传出;

因此,我们最初的想法是:有没有一种方式,降低 ViewController View Store 的依赖,只需要知道两者少量的接口,就能完成这种数据传输。同时,能避开 Delegate 这种数据传递方式呢?

Notification 可以实现这种需求,甚至可以直接跳过 ViewController 来传递数据,但这种方式显然是不适合的。 Notification 广播的属性决定了我们没办法将其控制在一个页面内。

KVO 呢?我们决定尝试一下。

KVO

苹果爸爸自身提供的 KVO 是开发者的一大槽点,在这不用过多解释。为了优化 KVO 糟糕的设计,社区有很多框架都对其进行了封装,以提供一套更友好的 KVO 接口,像 Facebook KVOController ,还有 Reactive Cocoa RACObserve 。在此,我们不去说明各种方案的优劣,只说结果:我们选择了 Reactive Cocoa KVO ,并做了部分改造。

依赖转移 和 Pipeline

有了改造后的 KVO 这一强大的工具,我们就可以尝试改造一下数据传输方式了。改造后的图如下所示:

图3:KVO 改造后的数据传输方式

这里的变化并不大,主要是 ViewController 通过 KVO 监听 View Store 的属性变化,然后来获取并传递数据。所以对实质的问题并没有改进的地方, ViewController 依然需要了解 View Store 很多信息,同时也并没有规避 View 层到 ViewController 数据层层传递的问题,所以这种改造没有太大意义。

问题的症结还是在于数据的传输必须经过 ViewController ,这样意味着 ViewController 必须强依赖于 View Store 。那能不能让数据绕过 ViewController ,通过其它方式在 View Store 之间传输呢?可能会有三种方案考虑:

  1. 通过 Notification 发送接收通知的方式来传输数据;
  2. View 层和 Store 层之间建立依赖关系,直接传输;
  3. 建立一个第三方对象,让 View Store 都依赖于这个对象,通过这个对象来传输数据;

第一种方案我们上面已经说过, Notification 可能会导致失控;第二种方案更不可行,我们使用 MVC 及各种衍生框架,就是为了让 View 业务层 相分离。所以,我们尝试了第三种方案,建立一个第三方对象。

换一个角度来考虑我们的分层框架,其实每一层都是在处理各种数据,根据所需的数据及其变化执行相应的操作,所以对数据的依赖是强需求。每一层只需要关心自己特定的数据,就能完成各自的职责。比如说 View 层只要有了列表数据,就可以展示一个 TableView ViewController 只要有了下一个 ViewController 所需要的数据,就可以跳转到下一个页面; Store 层有了用户名和密码,就可以发起登录请求。那么我们是不是可以构建一个 数据对象 ,这个数据对象包含 View ViewController Store 各层所需要的所有数据,让这三层都依赖于这个数据对象,然后各层按需从这个数据对象获取数据呢?基于这种考虑,我们引入了 Pipeline 对象。

  • Pipeline :可以理解为数据管道,也可以理解为数据集散中心。它负责组织一个页面分层层级所需要的所有数据。负责数据在 View 层与 Store 层之间的传输。实际上是一个数据对象,但不同于 Model 的职责,它主要负责数据传输,而不是数据表示。

注:这里只是借用了管道这个名称。和 Linux 中的管道不是一回事。

引入 Pipeline 后,我们的结构图就可以变成下面这种形式:

图4:MVCS + Pipeline

借助于 Pipeline KVO ,我们就可以让 View 层与 Store 层的数据传输绕开 ViewController ,通过这个数据管道来传输。以登录操作为例:用户在界面上输入 username password 后,点击登录按钮后,将这两个数据丢到 Pipeline 里面,同时丢到 Pipeline 里面的还有一个点击按钮标识 flag (注意这里有一个先后顺序)。 Store 层监听 Pipeline 对象的 flag 属性,发现其改变后,从 Pipeline 里面取出 username password ,发起登录请求,这个数据流转的路径如下:

图5:pipeline 数据传输路径

从示意图可以看到,这次数据传输和 ViewController 基本上没有啥关系。而且如果 Pipeline 设计的好的话, View 层每个视图层级的数据可以直接丢到 Pipeline 中进行传输,而不需要层层上传到外层视图对象上。

通过这种依赖转移,我们就可以弱化 ViewController View 层和 Store 层的依赖(注意是弱化,而不是消除,图4中我们通过虚线表示这种弱依赖关系), View 层和 Store 层只需要提供少量的接口,就可以让数据在三层间进行传输。

注: ViewController 本身也可能需要一些数据来执行某些操作,所以也可以依赖 Pipeline 并从中获取数据。

Minya 框架中,我们声明了一个 ViewController 的基类 MIViewController 。在这个类中,我们构造了 View ViewController Store 之间的弱依赖关系。我们先来看看这个类的主要代码:

MIViewController.h

@interface MIViewController : UIViewController

@property (nonatomic, strong, readonly, nonnull) id<MIStore> store;          //!< Store for the business logic
- (instancetype _Nullable)initWithStore:(id<MIStore> _Nonnull)store viewClass:(Class _Nonnull)viewClass callback:(MICallback _Nullable)callback;
- (instancetype






请到「今天看啥」查看全文


推荐文章
壹股经  ·  调整中寻机会!
昨天
壹股经  ·  调整中寻机会!
昨天
淘股吧  ·  DeepSeek,大消息!
3 天前
淘股吧  ·  牛市二波!又要吹爆了......
3 天前
一起神回复  ·  让男人抓狂的问题,再问就分手了!
7 年前
每日一文  ·  你的阅读造就了你
7 年前