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,它原本的样子应该是如此的

新普金娱乐 1

传统MVC

在那种架构下,View是无状态的,在Model变化的时候它只是简短的被Controller重绘,比如网页中你点击了一个新的链接,整个页面就再也加载。即便那种MVC在iOS应该里面可以达成,可是出于MVC的三个模块都密不可分耦合了,每一个模块都和任何二种模块有联系,所以即便是兑现了也尚未什么样含义。那种耦合还下降了它们的可重用性,所以,传统的MVC在iOS中可以甩掉了。

iOS
上边的架构形式你或许之前就询问过部分,接下去大家会帮您把它们举行一下梳理。大家先简要回想一下当下相比较主流的架构格局,分析比较一些他们的法则,并用有些小栗子来进展演习。就算您对内部的某一种相比较感兴趣的话,大家也在篇章里面给出了对应的链接。

Apple的MVC

新普金娱乐 2

Cocoa MVC

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

实际中,大家的MVC一般是如此的

新普金娱乐 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相对相比较难移植,即便只是富含弱业务,可是它说到底也是业务,迁移的时候很不难拔出罗布带出泥,也就是说它耦合了它的事体。而且软件是会成长的,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

新普金娱乐 4

MVP

看起来和Cocoa
MVC很像,也的确很像。可是,在MVC中View和COntroller是一环扣一环耦合的,而在MVP中,Presenter完全不关怀ViewController的生命周期,而且View也能被概括mock出来,所以在Presenter里面基本没有啥样布局相关的代码,它的职分只是通过数量和景况更新View。
并且在MVP中,UIVIewController的那些子类其实是属于View的。这样就提供了更好的可测性,只是开发速度会更高,因为您不可以不手动去创制数量和绑定事件。

上边我写了个简易的Demo

新普金娱乐 5

MVPDemo

鉴于此地关键是读书架构情势思想,所以我的命名简单无情,希望我们知道。

新普金娱乐 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程序员喜爱。

新普金娱乐 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:

新普金娱乐 8

MVVM

界面很简短,就是点击一个button修改label里面的多寡

新普金娱乐 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
    应该也是可以被测试的,可是或许因为它对 UIKit的器重,你会直接略过它。
  • 易用——它比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 都加载出来,那对单元测试来说太不利了。

实则,在模拟器(比如 HTC 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 到底是个怎么样事物呢?本质上来讲,他是单独于
UIKit 的, View 和 View 的景观的一个表现(representation)。ViewModel
能主动调用对 Model 做更改,也能在 Model
更新的时候对自家举行调整,然后经过 View 和 ViewModel 之间的绑定,对 View
也举行相应的换代。

绑定

自家在 MVP
的一部分简单的提过那一个情节,在此地让我们再延长切磋一下。绑定那几个概念来源于
OS X 平台的支出,但是在 iOS
平台方面,大家并从未相应的开发工具。当然,大家也有 KVO 和
文告,然则用那几个方法去做绑定不太便宜。

那就是说,如若我们不想协调去写他们的话,下边提供了三个选项:

  • 选一个根据 KVO 的绑定库,比如 RZDataBinding 或者 SwiftBond。
  • 利用全量级的 函数式响应编程 框架,比如 ReactiveCocoa、Rx斯威夫特 或者
    PromiseKit。

事实上,现在关系「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
    应该也是可以被测试的,可是可能因为它对 UIKit的依靠,你会直接略过它。
  • 易用 – 在我们的例证里面,它的代码量基本跟 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 UIKitindependent)相关的政工逻辑,可以调用 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
就能正常工作的页面,你一点一滴没有必要去重构它们,因为二种架构是一点一滴能够团结共处的。

相关文章