澳门1495娱乐iOS架构情势,架构格局

作为一个iOS程序员,MVC一定是我们了然的一种架构形式,而且当您的品类规模不大的时候,MVC也真正有它的优势,它的支付成效的确是十足高。但当您的门类提高的一定的规模,你会发觉传统的MVC形式会招致C层代码量剧增,维护困难等一序列问题,这么些时候大家就需要考虑部分其他形式了。

Make everything as simple as possible, but not simpler — Albert
Einstein
把每件事,做简单到极致,但又不过分简单 – 阿尔伯特(Bert)·爱因斯坦

MV(X)的基本要素

常用的架构形式

  • MVC
  • MVVM
  • MVP
  • VIPER

前面二种格局都由五个模块组成:

  • Models —— 数据层,负责数据的处理。
  • Views —— 显示层,即具有的UI
  • Controller/Presenter/ViewModele(控制器/显示器/视图模型)——它们背负View与Mode之间的调配

在利用 iOS 的 MVC 时候觉得蹊跷?想要尝试下 MVVM?从前传闻过
VIPER,不过又纠结是不是值得去学?

MVC

接轨读书,你就会清楚地方问题的答案 –
倘使读完了或者不通晓的话,欢迎留言评论。

传统的MVC

咱俩所熟谙的MVC其实Apple给我们提供的Cocoa
MVC,但实质上MVC起先爆发于Web,它原先的规范应该是如此的

澳门1495娱乐 1

传统MVC

在这种架构下,View是无状态的,在Model变化的时候它只是简短的被Controller重绘,比如网页中你点击了一个新的链接,整个页面就再也加载。固然这种MVC在iOS应该里面能够实现,不过由于MVC的多个模块都密不可分耦合了,每一个模块都和另外二种模块有关联,所以尽管是贯彻了也绝非什么意思。这种耦合还降低了它们的可重用性,所以,传统的MVC在iOS中得以吐弃了。

iOS
下边的架构模式你恐怕以前就领会过部分,接下去我们会帮你把它们举办一下梳理。我们先简要回顾一下当下相比主流的架构格局,分析相比一些他们的法则,并用一些小栗子来举行演习。假如您对中间的某一种相比感兴趣的话,我们也在随笔里面给出了相应的链接。

Apple的MVC

澳门1495娱乐 2

Cocoa MVC

Apple提供的MVC中,View和Model之间是相互独立的,它们只通过Controller来相互联系。可惜的是Controller得重用性太差,因为我们一般都把冗杂的政工逻辑放在了Controller中。

现实中,大家的MVC一般是这样的

澳门1495娱乐 3

现实MVC

何以会这么吗?紧要依然因为我们的UIViewController它本身就有所一个VIew,那些View是具备视图的根视图,而且View的生命周期也都由Controoler负责管理,所以View和Controller是很难完成互相独立的。尽管你可以把控制器里的有些事务逻辑和数量转换工作交给Model,不过你却并未艺术将一些行事让View来平摊,因为View的首要职责只是将用户的操作行为付出Controller去处理而已。于是Controller最终就变成了独具东西的代办和数据源,甚至还有网络请求…..还有……所以我们写的Controller代码量一般都是特别大的,随着当工作需求的扩大,Controller的代码量会一向增长,而相对来说View和Model的代码量就相比稳定,所以也有人把MVC叫做Massive
View Controller,因为Controller确实显得有些臃肿。

在这里关于Model的分开,其实有一个胖Model和瘦Model之分,它们的距离紧要就是把Controller的有的数据处理职责交给了胖Model。

胖Model(Fat Model):

胖Model包含了有些弱业务逻辑。胖Model要高达的目标是,Controller从胖Model这里拿到多大校来,不用做额外的操作仍然只做相当少的操作就能将数据利用在View上。
FatModel做了这一个弱业务之后,Controller可以变得相对skinny一点,它只需要关怀强业务代码。而强业务转移的可能性要比弱业务大得多,弱业务相对平稳,所以弱业务塞给Model不会有太大问题。另一方面,弱业务重新出现的效用要超过强业务,对复用性要求更高,如若这有的政工写在Controller,会造成代码冗余,类似的代码会洒拿到处都是,而且如若弱业务有改动,你就会需要修改所有地方。假若塞到了Model中,就只需要改Model就够了。
然则胖Mpdel也不是就是没有缺陷的,它的弱点就在于胖Model相对相比较难移植,尽管只是包含弱业务,但是它毕竟也是业务,迁移的时候很容易拔出Rob带出泥,也就是说它耦合了它的事体。而且软件是会成长的,FatModel也很有可能随着软件的成长尤为Fat,最终难以维护。

瘦Model(Slim Model):

瘦Model只担负作业数据的抒发,所有事务无论强弱一律人给Controller。瘦Model要高达的目的是,尽一切可能去编写细粒度Model,然后配套各类helper类或者措施来对弱业务做抽象,强业务依旧交给Controller。
出于Slim
Model跟工作完全无关,它的多寡足以交到其他一个能处理它多少的Helper或其他的目的,来成功作业。在代码迁移的时候独立性很强,很少会现出拔出萝卜带出泥的场馆。另外,由于SlimModel只是多少表明,对它举行保障基本上是0成本,软件膨胀得再厉害,SlimModel也不会大到哪儿去。缺点就在于,Helper这种做法也不翼而飞得很好,由于Model的操作会油不过生在各样地点,SlimModel很容易出现代码重复,在一定水平上违反了DRY(Don’t
Repeat
Yourself)的思绪,Controller依然不可防止在早晚水准上冒出代码膨胀。

综述,Cocoa MVC在各方面的变现如下:

  • 划分 – View 和 Model 确实是兑现了离别,可是 View 和 Controller
    耦合的太 厉害
  • 可测性 – 因为划分的不够明亮,所以能测的骨干就只有 Model 而已
  • 易用
    相较于其余格局,它的代码量最少。而且基本上每个人都很熟稔它,尽管是没太多经历的开发者也能保障。

对此设计格局的就学是一件容易上瘾的政工,所以先唤醒您刹那间:在您读完这篇小说之后,可能会比读在此之前有更多的问题,比如:

MVP

澳门1495娱乐 4

MVP

看起来和Cocoa
MVC很像,也确确实实很像。可是,在MVC中View和COntroller是紧密耦合的,而在MVP中,Presenter完全不关注ViewController的生命周期,而且View也能被略去mock出来,所以在Presenter里面基本没有怎么布局相关的代码,它的天职只是经过数量和气象更新View。
并且在MVP中,UIVIewController的那么些子类其实是属于View的。这样就提供了更好的可测性,只是开发速度会更高,因为你必须手动去创立数量和绑定事件。

上边我写了个简易的Demo

澳门1495娱乐 5

MVPDemo

是因为这里最重假如读书架构情势思想,所以自己的命名简单粗暴,希望大家明白。

澳门1495娱乐 6

界面1

界面也很简短,就是经过点击按钮修改三个label展现的始末

Model很简短,就是一个数据结构,但在事实上行使中,你可以将网络请求等部分数量处理放在这里

@interface Model : NSObject

@property (nonatomic, strong) NSString *first;
@property (nonatomic, strong) NSString *second;

@end

要让Presenter和View通信,所以我们定义一个磋商,以促成Presenter向View发送命令

@protocol MyProtocol <NSObject>

- (void)setFirst:(NSString *)first;
- (void)setSecond:(NSString *)second;

@end

view/VIewController,实现该协议

.h
 @interface ViewController : UIViewController

@property (nonatomic, strong) UILabel *firstLabel;
@property (nonatomic, strong) UILabel *secondLabel;
@property (nonatomic, strong) UIButton *tapButton;

@end


.m主要代码
- (void)viewDidLoad {
    [super viewDidLoad];
    [self.view addSubview:self.firstLabel];
    [self.view addSubview:self.secondLabel];
    [self.view addSubview:self.tapButton];
    self.presenter = [Presenter new];
    [self.presenter attachView:self];
}

- (void)buttonClicked{
    [self.presenter reloadView];
}

- (void)setFirst:(NSString *)first{
    self.firstLabel.text = first;
}

- (void)setSecond:(NSString *)second{
    self.secondLabel.text = second;
}

Presenter

.h
@interface Presenter : NSObject

- (void)attachView:(id <MyProtocol>)attachView;
- (void)reloadView;

@end


.m
@interface Presenter()

@property (nonatomic, weak) id <MyProtocol> view;
@property (nonatomic, strong) Model *model;

@end

@implementation Presenter

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.model = [Model new];
        self.model.first = @"first";
        self.model.second = @"second";
    }
    return self;
}

- (void)attachView:(id<MyProtocol>)attachView{
    self.view = attachView;
}

- (void)reloadView{
    //可以在这里做一些数据处理
    [self.view setFirst:self.model.first];
    [self.view setSecond:self.model.second];
}
@end

此地只是一个大概的Demo,其实想想很粗略,就是讲业务逻辑交给Presenter,而Presenter以命令的款型来支配View。
完整Demo可以看这里

(MVC)什么人来负担网络请求:是 Model 如故 Controller?

有的表达:

MVP架构拥有六个实在独立的分层,所以在组装的时候会有部分问题,而MVP也成了第一个披露这种问题的架构,因为大家不想让View知道Model的音信,所以在此时此刻的Controller去组装是不科学的,我们相应在另外的地方成功组建。比如我们得以创设一个应用层的Router服务,让它来负担组建和View-to-View的转场。这一个题目下众多格局中都设有。

下边总计一下MVP的各地点表现:

  • 划分——大家把大部分任务都分配到了Presenter和Model里面,而View基本不需要做什么
  • 可测性——咱们得以因此View来测试大部分事务逻辑
  • 易用——代码量差不多是MVC架构的两倍,但是MVP的思路依然蛮清晰的

此外,MVP还有一个变体,它的两样首要就是添加了数额绑定。这些本子的MVP的View和Model直接绑定,而Presenter依然此起彼伏处理View上的用户操作,控制View的显得变化。这种架构和传统的MVC类似,所以我们着力得以屏弃。

(MVVM)我该怎么去把一个 Model 传递给一个新创设的 View 的 ViewModel?

MVVM

MVVM可以说是MV(X)连串脑血吸虫病行兴起的也是最出色的一种架构,而它也广受我们iOS程序员喜爱。

澳门1495娱乐 7

MVVM

MVVM和MVP很像:

  • 把ViewController看成View
  • View和Model之间一向不紧耦合

除此以外它还让VIew和ViewModel做了数量绑定。ViewModel可以调用对Model做改变,也可以再Model更新的时候对自己举行调整,然后通过View和ViewModel之间的绑定,对View进行对应的翻新。

(VIPER)谁来承担创造 VIPER 模块:是 Router 仍旧 Presenter?

有关绑定

在iOS平台下面有KVO和文告,但是用起来总是认为不太便宜,所以有一部分三方库供我们挑选:

实质上,大家在关乎MVVM的时候就很容易想到ReactiveCocoa,它也是大家在iOS中拔取MVVM的最好工具。不过相对来说它的就学成本和维护成本
也是相比较高的,而且要是你使用不当,很可能引致灾难性的题目。

上面我暂时不用RAC来概括显示一下MVVM:

澳门1495娱乐 8

MVVM

界面很简单,就是点击一个button修改label里面的数码

澳门1495娱乐 9

界面

Model

@interface MVVMModel : NSObject

@property (nonatomic, copy) NSString *text;

@end

@implementation MVVMModel

- (NSString *)text{
    _text = [NSString stringWithFormat:@"newText%d",rand()];
    return _text;
}

ViewModel

@interface MVVMViewModel : NSObject

- (void)changeText;

@end

@interface MVVMViewModel()

@property (nonatomic, strong) NSString *text;
@property (nonatomic, strong) MVVMModel *model;

@end

@implementation MVVMViewModel

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.model = [MVVMModel new];
    }
    return self;
}

- (void)changeText{
    self.text = self.model.text;;
}

Controller

@interface MVVMViewController ()

@property (weak, nonatomic) IBOutlet UILabel *textLabel;
@property (nonatomic, strong) MVVMViewModel *viewModel;

@end

@implementation MVVMViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.viewModel = [[MVVMViewModel alloc]init];
    [self.viewModel addObserver:self forKeyPath:@"text" options:NSKeyValueObservingOptionNew context:nil];
}
- (IBAction)buttonClicked:(UIButton *)sender {
    [self.viewModel changeText];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    self.textLabel.text = change[@"new"];
}

MVVM的主导就是View和ViewModel的一个绑定,这里我只是简短的通过KVO实现,看起来并不是那么优雅,想要深度应用的话我觉着仍然有必要学习一下RAC的,需要总体的Demo请看这里

下边我们再来对MVVM的各方面呈现做一个评论:

  • 划分——MVVM 框架之中的 View 比 MVP
    里面负责的事情要更多一些。因为前端是透过 ViewModel
    的多寡绑定来更新自己境况的,而后者只是把装有的轩然大波统统付给 Presenter
    去处理就完了,自己我并不承担更新。
  • 可测性—— 因为 ViewModel 对 View
    是雾里看花的,那样大家对它的测试就变得很粗略。View
    应该也是力所能及被测试的,不过也许因为它对 UI基特(Kit)的看重性,你会直接略过它。
  • 易用——它比MVP会更加从简,因为在 MVP 下您不可能不要把 View
    的装有事件都付出 Presenter 去处理,而且亟需手动的去改进 View
    的情状;而在 MVVM 下,你只需要用绑定就足以化解。

综上:MVVM
真的很有魅力,因为它不只结合了上述二种框架的助益,还不需要你为视图的更新去写额外的代码(因为在
View 上曾经做了数额绑定),其余它在可测性上的表现也一如既往很棒。

为了简单易懂,以上的Demo都异常简洁,不明了看了这篇博客能否加深你对MV(X)的一部分知晓,这一个通晓也仅作为自己个人的一对参照,有咋样难堪的地方希望我们指出。


怎么要在意架构的选拔啊?

因为假若您不经意的话,难保一天,你就需要去调节一个宏大无比又有所各个问题的类,然后你会意识在这一个类里面,你一点一滴就找不到也修复不了任何
bug。一般的话,把这么大的一个类作为完全放在脑子里记着是一件分外艰辛的事务,你总是难免会忘掉一些相比较关键的底细。如若您发现在你的利用里面已经先河产出这种场所了,这您很可能境遇过上面这类问题:

  • 以此类是一个 UIViewController 的子类。
  • 你的多少直接保存在了 UIViewController 里面。
  • 你的 UIViews 好像什么都没做。
  • 您的 Model 只是一个纯粹的数据结构
  • 您的单元测试什么都不曾覆盖到

实则就是你依据了 Apple 的设计规范,实现了 Apple 的 MVC
框架,也仍然一样会遇上上边这一个问题;所以也没怎么好失落的。Apple 的 MVC
框架 有它自身的先天不足,然而那些大家前边再说。

让我们先来定义一下好的框架应该具有的特性:

  1. 用严谨定义的角色,平衡的将任务 划分 给不同的实业。
  2. 可测性
    常常取决于下面说的第一点(不用太操心,假如架构几时的话,做到这点并不难)。
  3. 易用 并且保养成本低。

干什么要分开?

当大家试图去领会事物的行事规律的时候,划分可以减轻大家的脑壳压力。如果您以为开发的越多,大脑就越能适应去处理千头万绪的干活,确实是这样。不过大脑的这种力量不是线性提升的,而且很快就会落得一个瓶颈。所以要处理千头万绪的事体,最好的不二法门如故在遵照单一责任标准 的准绳下,将它的任务分开到两个实体中去。

为啥要可测性?

对此这些对单元测试心存感激的人的话,应该不会有这上头的疑团:单元测试扶助她们测试出了新职能里面的谬误,或者是帮他们找出了重构的一个复杂类里面的
bug。这代表这多少个单元测试帮忙这么些开发者们在程序运行此前就发现了问题,这个题目假使被忽视的话很可能会提交到用户的装置上去;而修复那个题材,又至少需要一周左右的年华(AppStore
审核)。

何以要易用

这块没什么好说的,直说一些:最好的代码是那多少个从没被写出来的代码。代码写的越少,问题就越少;所以开发者想少写点代码并不一定就是因为他懒。还有,当你想用一个相比较聪明 的办法的时候,全完不要忽视了它的掩护成本。

MV(X) 的基本要素

近来大家面对架构设计形式的时候有了诸多抉择:

第一前三种格局都是把具有的实体归类到了下边两种分类中的一种:

  • Models(模型):数据层,或者负责处理数据的数量接口层。比如
    Person 和 PersonDataProvider 类
  • Views(视图):展现层(GUI)。对于 iOS 来说具有以 UI
    起始的类基本都属于这层。
  • Controller/Presenter/ViewModel(控制器/体现器/视图模型):它是
    Model 和 View 之间的胶水或者说是中间人。一般的话,当用户对 View
    有操作时它负责去修改相应 Model;当 Model
    的值爆发变化时它肩负去改进对应 View。

将实体举办分类之后我们可以:

  • 更好的知晓
  • 重用(主要是 View 和 Model)
  • 对它们独立的举办测试

让自身从 MV(X) 体系先导讲起,最终讲 VIPER。

MVC – 它原本的规范

在起来研究 Apple 的 MVC
以前,大家先来看下传统的MVC

在这种架构下,View 是无状态的,在 Model 变化的时候它只是简短的被
Controller
重绘;就像网页一样,点击了一个新的链接,整个网页就再一次加载。就算这种架构可以在
iOS 应用里面实现,但是由于 MVC
的两种实体被紧紧耦合着,每一种实体都和其他二种具有牵连,所以尽管是兑现了也绝非怎么意义。这种紧耦合还戏剧性的滑坡了它们被选定的或是,这说不定不是您想要在友好的施用里面来看的。综上,传统
MVC 的例子我以为也未尝必要去写了。

观念的 MVC 已经不相符立即的 iOS 开发了。

Apple 的 MVC

理想

View 和 Model 之间是互为独立的,它们只经过 Controller
来相互关系。有点恼人的是 Controller
是重用性最差的,因为我们一般不会把冗杂的事务逻辑放在 Model
里面,这就只可以放在 Controller 里了。

辩论上看这样做一般挺简单的,可是你有没有觉得多少难堪?你居然听过有人把
MVC 叫做重控制器格局。此外关于 ViewController
瘦身

已经成为 iOS 开发者们热议的话题了。为何 Apple
要沿用只是做了一点点改良的历史观 MVC 架构呢?

现实

Cocoa MVC 鼓励你去写重控制器是因为 View
的全方位生命周期都亟需它去管理,Controller 和 View
很难形成相互独立。即便你能够把控制器里的一些政工逻辑和数量转换的工作交给
Model,但是你再想把肩负往 View 里面分摊的时候就没办法了;因为 View
的首要职责就只是讲用户的操作行为付出 Controller 去处理而已。于是
ViewController
最后就成为了具备东西的代办和数据源,甚至还背负网络请求的发起和废除,还有…剩下的你来讲。

像下边这种代码你应该不生疏吧:

var userCell = tableView.dequeueReusableCellWithIdentifier("identifier") as UserCell
userCell.configureWithUser(user)

Cell 作为一个 View 直接用 Model 来形成了自身的布置,MVC
的尺度被打破了,这种景观从来留存,而且还没人觉得有如何问题。要是你是严峻遵照MVC 的话,你应有是在 ViewController 里面去安排 Cell,而不是直接将 Model
丢给 Cell,当然如此会让您的 ViewController 更重。

Cocoa MVC 被戏称为重控制器格局仍然有来头的。

问题直到起先单元测试(希望你的类别里面早已有了)之后才起来显现出来。Controller
测试起来很劳顿,因为它和 View 耦合的太狠心,要测试它的话就需要反复的去
mock View 和 View
的生命周期;而且遵照这种架构去写控制器代码的话,业务逻辑的代码也会因为视图布局代码的来由而变得很糊涂。

我们来看上面这段 playground 中的例子:

import UIKit

struct Person { // Model
    let firstName: String
    let lastName: String
}

class GreetingViewController : UIViewController { // View + Controller
    var person: Person!
    let showGreetingButton = UIButton()
    let greetingLabel = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.showGreetingButton.addTarget(self, action: "didTapButton:", forControlEvents: .TouchUpInside)
    }

    func didTapButton(button: UIButton) {
        let greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName
        self.greetingLabel.text = greeting

    }
    // layout code goes here
}
// Assembling of MVC
let model = Person(firstName: "David", lastName: "Blaine")
let view = GreetingViewController()
view.person = model;

MVC 的组建,可以置身脚下正值展现的 ViewController 里面

这段代码看起来不太好测试对啊?我们得以把 greeting
的转变方法放到一个新类 GreetingModel
里面去独立测试。可是我们只要不调用与 View 相关的艺术的话
viewDidLoad, didTapButton),就测试不到 GreetingViewController
里面任何的来得逻辑(尽管在地方那么些例子里面,逻辑已经很少了);而调用的话就可能需要把具有的
View 都加载出来,这对单元测试来说太不利了。

实则,在模拟器(比如 HUAWEI 4S)上运行并测试 View
的来得并不可以确保在任何设施上(比如
三星GALAXY Tab)也能好好运转。所以自己提议把「Host
Application」从您的单元测试配置项里移除掉,然后在不启动模拟器的景象下来跑你的单元测试。

View 和 Controller 之间的互相,并不可以真的的被单元测试覆盖。

补充:What’s Worth Unit Testing in Objective-C
?

综合,Cocoa MVC
貌似并不是一个很好的取舍。不过大家仍然评估一下他在各方面的展现(在作品起始有讲):

  • 划分 – View 和 Model 确实是落实了分别,不过 View 和 Controller
    耦合的太狠心
  • 可测性 – 因为划分的不够了解,所以能测的中坚就只有 Model 而已
  • 易用
    相较于此外格局,它的代码量最少。而且大多每个人都很熟谙它,固然是没太多经历的开发者也能珍重。
    在这种情景下你可以挑选 Cocoa
    MVC:你并不想在架设上消费太多的光阴,而且你觉得对于你的小品种以来,花费更高的保安资金只是浪费而已。

假使你最重视的是付出进度,那么 Cocoa MVC 就是您最好的选拔。

MVP – 保证了任务分开的(promises delivered) Cocoa MVC

看起来的确很像 Apple 的 MVC 对吧?确实蛮像,它的名字是 MVP(被动变化的
View)。稍等…这一个意思是说 Apple 的 MVC 实际上是 MVP
吗?不是的,回忆一下,在 MVC 里面 View 和 Controller
是耦合紧密的,可是对于 MVP 里面的 Presenter 来讲,它完全不关心
ViewController 的生命周期,而且 View 也能被概括 mock 出来,所以在
Presenter 里面基本没什么布局相关的代码,它的职责只是通过数量和情形更新
View。

万一我跟你讲 UIViewController 在此地的角色其实是 View
你觉得什么。

在 MVP 架构里面,UIViewController 的那个子类其实是属于 View 的,而不是
Presenter。这种区别提供了极好的可测性,不过这是用支付速度的代价换到的,因为您不可以不要手动的去创造数量和绑定事件,像下边这段代码中做的同样:

import UIKit

struct Person { // Model
    let firstName: String
    let lastName: String
}

protocol GreetingView: class {
    func setGreeting(greeting: String)
}

protocol GreetingViewPresenter {
    init(view: GreetingView, person: Person)
    func showGreeting()
}

class GreetingPresenter : GreetingViewPresenter {
    unowned let view: GreetingView
    let person: Person
    required init(view: GreetingView, person: Person) {
        self.view = view
        self.person = person
    }
    func showGreeting() {
        let greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName
        self.view.setGreeting(greeting)
    }
}

class GreetingViewController : UIViewController, GreetingView {
    var presenter: GreetingViewPresenter!
    let showGreetingButton = UIButton()
    let greetingLabel = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.showGreetingButton.addTarget(self, action: "didTapButton:", forControlEvents: .TouchUpInside)
    }

    func didTapButton(button: UIButton) {
        self.presenter.showGreeting()
    }

    func setGreeting(greeting: String) {
        self.greetingLabel.text = greeting
    }

    // layout code goes here
}
// Assembling of MVP
let model = Person(firstName: "David", lastName: "Blaine")
let view = GreetingViewController()
let presenter = GreetingPresenter(view: view, person: model)
view.presenter = presenter

至于组建方面的机要表明

MVP 架构拥有两个真正独立的分层,所以在组建的时候会有部分题目,而 MVP
也成了第一个披露了这种问题的架构。因为我们不想让 View 知道 Model
的音信,所以在时下的 ViewController(角色其实是
View)里面去举行组装肯定是不科学的,我们应该在另外的地点成功组建。比如,我们可以创造一个应用层(app-wide)的
Router 服务,让它来负担组建和 View-to-View 的转场。这些题材不仅在 MVP
中留存,在接下去要介绍的格局里面也都有这些问题。

让我们来看一下 MVP 在各地点的呈现:

  • 划分 – 我们把大部分的任务都分配到了 Presenter 和 Model 里面,而
    View 基本上不需要做咋样(在上头的例子里面,Model 也什么都没做)。
  • 可测性 – 简直棒,我们得以因此 View 来测试大部分的事务逻辑。
  • 易用 – 就大家地点万分简单的例子来讲,代码量差不多是 MVC
    架构的两倍,不过 MVP 的思路依然蛮清晰的。

MVP 架构在 iOS 中代表极好的可测性和光辉的代码量。

MVP – 添加了数量绑定的另一个本子

还留存着另一种的 MVP – Supervising Controller MVP。这些版本的 MVP 包括了
View 和 Model 的一贯绑定,与此同时 Presenter(Supervising
Controller)依旧连续处理 View 上的用户操作,控制 View 的来得变化。

不过咱们事先讲过,模糊的任务分开是不佳的业务,比如 View 和 Model
的紧耦合。这多少个道理在 Cocoa 桌面应用开发方面也是一律的。

就像传统 MVC
架构一样,我找不到有如何理由需要为那个有瑕疵的架构写一个事例。

MVVM – 是 MV(X) 体系架构里面最新兴的,也是最特出的

MVVM
架构是 MV(X) 里面最新的一个,让大家期待它在产出的时候曾经考虑到了 MV(X)
形式以前所境遇的题目吗。

辩护上来说,Model – View – ViewModel 看起来异常棒。View 和 Model
我们已经都谙习了,中间人的角色大家也熟知了,可是在这里中间人的角色成为了
ViewModel。

它跟 MVP 很像:

  • MVVM 架构把 ViewController 看做 View。
  • View 和 Model 之间没有紧耦合

除此以外,它还像 Supervising 版的 MVP 这样做了数量绑定,可是本次不是绑定
View 和 Model,而是绑定 View 和 ViewModel。

那么,iOS 里面的 ViewModel 到底是个什么东西呢?本质上来讲,他是独立于
UI基特 的, View 和 View 的景观的一个突显(representation)。ViewModel
能主动调用对 Model 做改变,也能在 Model
更新的时候对自己举办调整,然后经过 View 和 ViewModel 之间的绑定,对 View
也进展相应的立异。

绑定

我在 MVP
的局部简单的提过这么些情节,在那边让大家再延长商量一下。绑定那多少个定义来源于
OS X 平台的开发,可是在 iOS
平台方面,我们并不曾对应的开发工具。当然,我们也有 KVO 和
公告,然而用这么些措施去做绑定不太有利。

那么,要是大家不想自己去写他们来说,上边提供了三个采取:

  • 选一个依据 KVO 的绑定库,比如 RZDataBinding 或者 斯威夫特Bond。
  • 使用全量级的 函数式响应编程 框架,比如 ReactiveCocoa、RxSwift 或者
    Promise基特。

实际上,现在关系「MVVM」你应当就会想到
ReactiveCocoa,反过来也是同一。即便大家可以透过简单的绑定来兑现 MVVM
情势,不过 ReactiveCocoa(或者同类型的框架)会让你更大限度的去精晓MVVM。

响应式编程框架也有好几不佳的地点,能力越大责任越大嘛。用响应式编程用得糟糕的话,很容易会把工作搞得一团糟。或者这么说,假若有何地出错了,你需要花费更多的年月去调节。看着下面这张调用堆栈图感受一下:

在接下去的这多少个小例子中,用响应式框架(FRF)或者 KVO
都展示有些大刀小用,所以我们用另一种方法:直接的调用 ViewModel 的
showGreeting 方法去革新自己(的 greeting 属性),(在 greeting
属性的 didSet 回调里面)用 greetingDidChange 闭包函数去改进 View
的来得。

import UIKit

struct Person { // Model
    let firstName: String
    let lastName: String
}

protocol GreetingViewModelProtocol: class {
    var greeting: String? { get }
    var greetingDidChange: ((GreetingViewModelProtocol) -> ())? { get set } // function to call when greeting did change
    init(person: Person)
    func showGreeting()
}

class GreetingViewModel : GreetingViewModelProtocol {
    let person: Person
    var greeting: String? {
        didSet {
            self.greetingDidChange?(self)
        }
    }
    var greetingDidChange: ((GreetingViewModelProtocol) -> ())?
    required init(person: Person) {
        self.person = person
    }
    func showGreeting() {
        self.greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName
    }
}

class GreetingViewController : UIViewController {
    var viewModel: GreetingViewModelProtocol! {
        didSet {
            self.viewModel.greetingDidChange = { [unowned self] viewModel in
                self.greetingLabel.text = viewModel.greeting
            }
        }
    }
    let showGreetingButton = UIButton()
    let greetingLabel = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.showGreetingButton.addTarget(self.viewModel, action: "showGreeting", forControlEvents: .TouchUpInside)
    }
    // layout code goes here
}
// Assembling of MVVM
let model = Person(firstName: "David", lastName: "Blaine")
let viewModel = GreetingViewModel(person: model)
let view = GreetingViewController()
view.viewModel = viewModel

接下来,大家再回过头来对它各方面的显示做一个评论:

  • 划分 – 这在大家的小栗子里面表现的不是很精晓,但是 MVVM
    框架之中的 View 比 MVP 里面负责的政工要更多一些。因为前者是因此ViewModel 的数量绑定来更新自己情形的,而后人只是把具有的轩然大波统统付给
    Presenter 去处理就完了,自己本身并不担负更新。
  • 可测性 – 因为 ViewModel 对 View
    是未知的,这样我们对它的测试就变得很简单。View
    应该也是力所能及被测试的,可是或许因为它对 UI基特的借助,你会间接略过它。
  • 易用 – 在大家的例子里面,它的代码量基本跟 MVP
    持平,不过在实质上的运用当中 MVVM 会更简洁一些。因为在 MVP
    下你不可能不要把 View 的兼具事件都交由 Presenter
    去处理,而且需要手动的去改进 View 的情况;而在 MVVM
    下,你只需要用绑定就可以缓解。
    MVVM
    真的很有魅力,因为它不仅仅结合了上述二种框架的优点,还不需要你为视图的更新去写额外的代码(因为在
    View 上业已做了多少绑定),此外它在可测性上的显示也照例很棒。

VIPER – 把搭建乐高积木的经验运用到 iOS 应用的计划上

VIPER 是大家最后一个要介绍的框架,这些框架相比较好玩的是它不属于其他一种
MV(X) 框架。

到近年来停止,你或许以为我们把任务分开成三层,这么些颗粒度已经很不利了啊。现在
VIPER 从另一个角度对职责举办了划分,本次划分了 五层

  • Interactor(交互器)
    包括数据(Entities)或者网络有关的业务逻辑。比如创造新的 entities
    或者从服务器上获取数据;要促成这一个效用,你恐怕会用到部分劳务和保管(Services
    and Managers):那一个或许会被误以为成是表面依赖东西,不过它们就是
    VIPER 的 Interactor 模块。
  • Presenter(展示器) – 包括 UI(but UI基特(Kit)independent)相关的业务逻辑,可以调用 Interactor 中的方法。
  • Entities(实体) – 纯粹的多寡对象。不包括数据访问层,因为这是
    Interactor 的职责。
  • Router(路由) – 负责 VIPER 模块之间的转场

事实上 VIPER
模块可以只是一个页面(screen),也得以是您利用里所有的用户使用流程(the
whole user story)-
比如说「验证」那么些职能,它可以只是一个页面,也得以是接连相关的一组页面。你的每个「乐高积木」想要有多大,都是你协调来支配的。

假如我们把 VIPER 和 MV(X)
序列做一个比照的话,咱们会意识它们在任务分开下边有下边的片段区分:

  • Model(数据交互)的逻辑被更换来了 Interactor 里面,Entities
    只是一个如何都并非做的数码结构体。
  • Controller/Presenter/ViewModel 的天职里面,只有 UI
    的彰显效果被更换来了 Presenter 里面。Presenter
    不持有直接改动数据的力量。
  • VIPER 是首先个把导航的职责单独划分出来的架构情势,负责导航的就是
    Router 层。

如何正确的施用导航(doing routing)对于 iOS
应用开发以来是一个挑衅,MV(X)
体系的架构完全就不曾意识到(所以也不用处理)那个问题。

下面的这一个列子并从未关联到导航和 VIPER 模块间的转场,同样下边 MV(X)
系列架构里面也都尚未涉嫌。

import UIKit

struct Person { // Entity (usually more complex e.g. NSManagedObject)
    let firstName: String
    let lastName: String
}

struct GreetingData { // Transport data structure (not Entity)
    let greeting: String
    let subject: String
}

protocol GreetingProvider {
    func provideGreetingData()
}

protocol GreetingOutput: class {
    func receiveGreetingData(greetingData: GreetingData)
}

class GreetingInteractor : GreetingProvider {
    weak var output: GreetingOutput!

    func provideGreetingData() {
        let person = Person(firstName: "David", lastName: "Blaine") // usually comes from data access layer
        let subject = person.firstName + " " + person.lastName
        let greeting = GreetingData(greeting: "Hello", subject: subject)
        self.output.receiveGreetingData(greeting)
    }
}

protocol GreetingViewEventHandler {
    func didTapShowGreetingButton()
}

protocol GreetingView: class {
    func setGreeting(greeting: String)
}

class GreetingPresenter : GreetingOutput, GreetingViewEventHandler {
    weak var view: GreetingView!
    var greetingProvider: GreetingProvider!

    func didTapShowGreetingButton() {
        self.greetingProvider.provideGreetingData()
    }

    func receiveGreetingData(greetingData: GreetingData) {
        let greeting = greetingData.greeting + " " + greetingData.subject
        self.view.setGreeting(greeting)
    }
}

class GreetingViewController : UIViewController, GreetingView {
    var eventHandler: GreetingViewEventHandler!
    let showGreetingButton = UIButton()
    let greetingLabel = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.showGreetingButton.addTarget(self, action: "didTapButton:", forControlEvents: .TouchUpInside)
    }

    func didTapButton(button: UIButton) {
        self.eventHandler.didTapShowGreetingButton()
    }

    func setGreeting(greeting: String) {
        self.greetingLabel.text = greeting
    }

    // layout code goes here
}
// Assembling of VIPER module, without Router
let view = GreetingViewController()
let presenter = GreetingPresenter()
let interactor = GreetingInteractor()
view.eventHandler = presenter
presenter.view = view
presenter.greetingProvider = interactor
interactor.output = presenter

俺们再来评价下它在各地点的展现:

  • 划分 – 毫无疑问的,VIPER 在任务分开方面是做的最好的。
  • 可测性 – 理所当然的,职责分开的越好,测试起来就越容易
  • 易用
    最终,你恐怕早就猜到了,下面两点好处都是用维护性的代价换到的。一个很小的天职,可能就需要你为各品种写大量的接口。

那么,大家到底应该给「乐高」一个如何的评论呢?

倘使你在动用 VIPER
框架的时候有一种在用乐高积木搭建帝国大厦的感觉到,那么你也许
正在犯错误;可能对于你承担的应用来说,还未曾到使用 VIPER
的时候,你应该把一些作业考虑的再简单一些。总是有一些人不经意这么些题材,继续扛着大炮去打小鸟。我觉得可能是因为他俩相信,尽管眼下来看维护成本高的答非所问常理,不过至少在未来他们的使用可以从
VIPER 架构上得到回报吧。假诺你也跟她俩的观点一致的话,这我指出您品味一下
Generamba – 一个可以转移 VIPER
框架的工具。就算对于我个人来讲,这感觉就像给大炮装上了一个机动瞄准系统,然后去做一件只用弹弓就能解决的业务。

结论

我们大概领悟了两种架构模式,对于这多少个让你困惑的题材,我期待您早就找到了答案。可是一定,你应当已经意识到了,在挑选架构情势这件问题方面,不存在哪些
银色子弹,你需要做的就是具体情状具体分析,权衡利弊而已。

就此在同一个利用里面,即便有三种混合的架构格局也是很正常的一件业务。比如:先导的时候,你用的是
MVC 架构,后来您意识到有一个异样的页面用 MVC
做的的话维护起来会一定的难为;这一个时候你可以只针对那多少个页面用 MVVM
格局去支付,对于往日这么些用 MVC
就能正常办事的页面,你完全没有必要去重构它们,因为二种架构是一心可以协调共处的。

相关文章