一.架构模式
1.引言
架构模式,用于描述软件系统里的基本的结构组织或纲要。架构模式提供了一些定义好的子系统,指定它们的责任,并给出把它们组织在一起的法则和指南。常见的架构模式有:分层模式、微核模式、管道与过滤器、MVC模式、REST模式、SOA模式。
2.起源
软件开发中需求变动往往导致代码的修改,而一些代码尤其是大型系统的码理解起来并不容易,只有当时的开发者比较清楚,其他人很难接手。修改这种代码时往往会碰到“触一发而动全身”的问题,因为有“代码耦合”问题。代码耦合让整个系统变得难以理解、修改、分工、集成。针对耦合问题软件界进行了大量的理论研究和实践,最后发现:系统的架构设计,是改善耦合的最好方式~
3.特点
优秀架构模式的共同点:
- 任务均衡分摊给具有清晰角色的实体;
- 高内聚、低耦合;
- 高重用性、低维护成本;
- 可单独测试。
4.区分
区分两个名词:架构模式
与设计模式
:
设计模式
是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。设计模式一共有23种,常见的有工厂模式、单例模式、代理模式、观察者模式、策略模式等。
一个构架往往用到多个设计模式,如mvc
架构是观察者模式、策略模式和组合模式的演变。
上图是传统的MVC
架构,项目中的代码被整合为视图
、模型
、控制器
三个角色。
- 当用户操作一个视图时,会产生一个事件并交给控制器;
- 控制器收到事件后会采用一种
策略
:要求模型更新数据的状态或者要求视图更新样式;
- 模型中数据变化后则会
通知
视图更新界面。
二.MVC
1.Apple版MVC
上图是传统的MVC模式,虽然也区分了三种角色,但是它们互相之间都有耦合,尤其是模型和视图之间。而视图和模型往往是最需要复用的,所以,最合理的设计是保持二者的独立性。因此 Apple 设计了自己的MVC版本,通过控制器来实现视图与模型的解耦。
2.角色划分
模型
持有数据、封装处理数据的业务,为控制器
提供数据接口。模型
不应与视图
层有任何关联。
视图
展示数据和信息并允许用户编辑数据。
视图
应像UIButton一样可复用、可配置并保持统一性。
视图
不能绑定模型
,所以视图
需要一种机制来知晓模型
中数据的变化。
控制器
控制器
作为中间人连接模型
与视图
。
为视图
提供需要显示的数据;
响应用户的操作并调用模型
中的业务更新数据;将数据的变化通知给视图。
控制器也用来管理对象的生命周期,定义相关业务以实现一些设置和指定的任务。
3.现实中的MVC
理想中视图与控制器应相互独立,但实际开发过程中视图层往往紧密耦合在控制层里:控制器要负责维护视图的生命周期,响应用户在视图层的操作,实现诸如Tableview
等视图的代理等,MVC
事实上变成了M-VC
。
理想中视图与模型应相互独立,但实际中可能会遇到这总情形:初始化Cell
时经常会直接传入Model
对象,形成视图与模型的耦合。
1 2 3 4 5 6 7
| - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { CustomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Goods"]; cell.goods = self.dataSource[indexPath.row]; return cell; }
|
理想中模型应持有数据、封装处理数据的业务、提供数据接口,但实践中模型被简单的设计为只有一些属性的类,数据相关的业务都被放在了控制器中。当需要修改数据的业务逻辑时,如更换网络通讯库、更新json解析时的某个字段,就需要在控制器中进行修改。
理想中上面的Cell
不应引用模型对象,那么Cell
的设置就要放到控制器中,这会增加控制器中的代码量。另外像网络请求、工具方法等不应放在视图和模型层里,而放在控制器里也会造成控制器的臃肿。这些就是Massive View Controller
的由来,形象表示如下:
当业务逻辑和展示逻辑都在控制器中时,很难进行单元测试。所以控制器中只应存放一些不能复用的代码,其他代码尽量分离出去。
不能复用的代码包括:
- 初始化View 和 Model的语句;
- 根据View 层用户操作调用 Model 层处理数据的语句;
- 用来接收 Model 层数据的回调、代理,和通知 View 层更新视图的逻辑;
可分离的代码包括:
- 在自定义类中搭建视图结构,通过代理将用户操作回调给控制器;
- 将诸如TableView的代理抽离到单独的类中(参考AFN中请求回调的处理方式);
- 将转换 Model 层回调数据的业务抽离到单独的类中;
三.MVP
1.角色划分
模型
与 MVC 中的模型类似。
视图
由”视图+控制器“组成,作用:
界面元素布局和执行动画;
将用户操作事件交给P
层处理并展示处理结果;
P层中的业务逻辑处理完毕后通过代理告知视图
刷新界面;
控制器负责生成视图,实现视图的代理和数据源以及界面的跳转等。
P层
作为视图
与模型
的中间人,实现视图与模型的解耦:
定义响应视图中事件的接口,实现事件对应的处理逻辑;
调用模型的接口以获取数据,加工数据并将其封装成视图适用的数据和状态。
MVP 由 MVC 演化而来,它对 MVC 中的控制器进行了优化而生成Presenter
。P层
与 MVC 中的控制器一样负责核心逻辑。不一样的是P层
通过接口和协议传递数据,从而使视图和模型解耦,更加专注于自身业务逻辑。因为主要的逻辑在P层
,所以可以方便的对其进行单元测试。
2.示例:
一个简单场景:请求加载一页数据;进度指示器根据加载状态显示/隐藏。
V层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| #import "ViewController.h" #import "Presenter.h" #import "UserViewProtocol.h"
@interface ViewController () <UserViewProtocol, UITableViewDataSource>
@property (nonatomic,strong) NSArray *friendlyUIData; @property (nonatomic,strong) Presenter *presenter; @property (weak, nonatomic) IBOutlet UITableView *tableview; @property (weak, nonatomic) IBOutlet UIActivityIndicatorView *indicator;
@end
@implementation ViewController
- (void)viewDidLoad { [super viewDidLoad]; self.presenter = [Presenter new]; [self.presenter confDelegate:self]; [self.presenter fetchData]; }
#pragma mark -Tableview datasource - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.friendlyUIData.count; }
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath]; cell.textLabel.text = [self.friendlyUIData[indexPath.row] valueForKey:@"name"];
return cell; }
#pragma mark -UserView Protocol
-(void)userViewDataSource:(NSArray*)data { self.friendlyUIData = data; [self.tableview reloadData]; }
-(void) showIndicator { self.indicator.hidden = NO; }
- (void) hideIndicator { self.indicator.hidden = YES; }
|
ViewController
与tableview
构成了 MVP 中的视图层,负责界面的显示和更新。它实现了tableview
的协议及用户交互的协议UserViewProtocol
,等待 Presenter
的命令被动更新UI。
P层
1 2 3 4 5 6 7 8
| #import <Foundation/Foundation.h> #import "UserViewProtocol.h"
@interface Presenter : NSObject -(void)confDelegate:(id <UserViewProtocol>)view; -(void)fetchData; @end
|
Presenter中定义外部接口,供View层调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| #import "Presenter.h" #import "UserService.h"
@interface Presenter() @property (nonatomic,strong) UserService *userService; @property (nonatomic,weak) id <UserViewProtocol> mDelegate; @end
@implementation Presenter
- (void)confDelegate:(id<UserViewProtocol>)view { self.mDelegate = view; self.userService = [UserService new]; }
-(void)fetchData{ [self getUserDatas]; }
-(void)getUserDatas{ [self.mDelegate showIndicator]; [_userService getUserInfosSuccess:^(NSDictionary *dic) { [self.mDelegate hideIndicator]; [self.mDelegate userViewDataSource:[self processOriginDataToUIFriendlyData:userArr]]; } andFail:^(NSDictionary *dic) { }]; }
-(NSArray *)processOriginDataToUIFriendlyData:(NSArray *) originData { return friendlyUIData; } @end
|
其中的-confDelegate方法中有两个作用:
- 将实现了
UserViewProtocol
协议的对象绑定到 Presenter 上。
- 持有一个
M层
对象(UserService)以发起网络请求。
1 2 3 4 5 6 7 8 9 10
| #import <Foundation/Foundation.h>
@protocol UserViewProtocol <NSObject>
-(void) userViewDataSource:(NSArray*)data; -(void) showIndicator; -(void) hideIndicator;
@end
|
这个协议是P层的一部分,其中定义的方法就是P层
对视图
层发送的命令。View
层实现了此协议,在Model
层拿到数据并通过block返回给P层
后,P层
会通过代理,通知View
层更新UI。
M层
1 2 3 4 5 6 7 8 9
| #import <Foundation/Foundation.h> typedef void(^SuccessHandler)(NSDictionary *dic); typedef void(^FailHandler)(NSDictionary *dic);
@interface UserService : NSObject -(void)getUserInfosSuccess:(SuccessHandler )success andFail:(FailHandler) fail; @end
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #import "UserService.h"
@implementation UserService
-(void)getUserInfosSuccess:(SuccessHandler )success andFail:(FailHandler) fail { NSArray *result =@[@{@"name":@"Tom",@"age":@25}]; dispatch_after(dispatch_time( DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ success(@{@"data":result}); }); } @end
|
UserService属于M层
,用来请求数据给P层
。当M层
数据发生变化时(如网络请求返回数据),通过block
回调给P层
,P层
再将处理后的数据通过代理
反馈给View层
并由后者更新界面。
使用 MVP 会增加代码量,但是总体上职责清晰,各层通过定义的API、代理或block回调进行通信,减少了代码的复杂性。
三.MVVM
1.角色划分
模型
与 MVP 中的模型类似。
视图
View+Controller组成,负责显示UI。
绑定ViewModel
中的属性,触发ViewModel
中的命令。
ViewModel
负责用户输入内容的验证逻辑(如用户名、密码的合法性);
视图显示逻辑;暴露公开的属性和命令供视图
层进行绑定;
从Model
层获取数据,转换成视图
层可展示的数据(如TableView 的 DataSource)。
Binder
在 MVVM 中,声明式的数据和命令绑定是一个隐含的约定,它可以让开发者非常方便地实现视图
层和ViewModel
的同步,避免编写大量繁杂的胶水代码。著名的RAC库就提供了这种绑定能力。
2.角色细解
数据绑定
MVVM是微软提出的,是在 MVP 的基础上发展起来的。因此 MVVM 各层的职责基本上与 MVP 的类似,其中VM
对应P层
。MVVM 相对于 MVP 改良了什么呢?答案就是 数据绑定
。
在 MVP 中,从用户点击开始,一个完整的响应流程是:视图
调用P层
处理业务逻辑,P层
调用模型
处理数据,处理完成后 P层
再通过代理回调视图
。这里要反复同步P层
的状态,当事件多起来时这样写就有点麻烦。
而在 MVVM 中, View
与VM层
之间多了数据绑定的操作,这意味着当VM层
的数据变化时,你只需要更新VM层
的某个属性,那么绑定了该属性的视图
层会相应的更新UI,自动实现状态的同步。
ViewModel
MVVM 模式中,ViewModel
存在的目的在于抽离Controller
中的展示业务逻辑,而不是代替Controller
本身。所以它不负责视图的操作,也就不需要持有任何的视图对象,更不能包含视图的push/present
等跳转逻辑。这里,Controller 要做的仅是将视图
和ViewModel
进行绑定,同时管理各个视图
。所以实际上我们的 MVVM 也可以看成M-VM-C-V
。
3.示例1:OC
场景:点击刷新按钮,请求数据;进度指示器根据请求状态显示/隐藏;
View层与绑定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
| @interface ViewControllerII () <UITableViewDataSource, UITableViewDelegate>
@property (weak, nonatomic) IBOutlet UITableView *mTableview; @property (weak, nonatomic) IBOutlet UIActivityIndicatorView *mIndicator; @property (nonatomic, strong) RACViewModel *mViewModel;
@end
@implementation ViewControllerII
- (void)viewDidLoad { [super viewDidLoad]; [self setUps]; [self bindViewModel]; }
#pragma mark -BUsiness
- (void)setUps{ _mIndicator.hidden = YES; UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom]; [btn setTitle:@"reload" forState:UIControlStateNormal]; [btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; [btn sizeToFit];
[[btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) { @strongify(self) self.mIndicator.hidden = NO; [self.mViewModel.fetchCommand execute:nil]; }]; self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:btn]; self.mTableview.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero]; }
- (void)bindViewModel{ @weakify(self) [RACObserve(self.mViewModel, canReload) subscribeNext:^(id x) { @strongify(self); [self.mTableview canReload]; self.mIndicator.hidden = YES; }];
[RACObserve(self.mIndicator, hidden) subscribeNext:^(NSNumber *hiddenNum) { @strongify(self); BOOL hidden = hiddenNum.boolValue; if (hidden) { [self.mIndicator stopAnimating]; }else{ [self.mIndicator startAnimating]; } }]; }
- (RACViewModel *)mViewModel{ if (!_mViewModel) { _mViewModel = [[RACViewModel alloc] init]; } return _mViewModel; }
#pragma mark -Tableview Data&Delegate -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{ return 1; }
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ return self.mViewModel.cellNums; }
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; UILabel *label = [cell viewWithTag:1]; label.text = [self.mViewModel convertedTextAtIndexPath:indexPath];
return cell; } @end
|
以上是View
层,由ViewController与其中的tableView、按钮、进度小菊花共同组成;
这里创建并持有VM
层对象mViewModel
,同时绑定了VM层的属性(canReload);
点击按钮即会向VM
层的fetchCommand
发送指令请求数据;
VM
层拿到数据后,View
层不再需要代理或block,而是直接在订阅的VM
层信号回调中更新UI;
更新UI所需的数据及其转换等业务逻辑均交由VM
层处理。
ViewModel
头文件:
1 2 3 4 5 6 7 8 9 10 11 12 13
| #import "RACCommand.h" #import "RACSignal.h"
@interface RACViewModel : NSObject
@property (nonatomic, readonly) NSUInteger cellNums; @property (nonatomic, readonly) BOOL canReload; @property (nonatomic, readonly, strong) RACCommand *fetchCommand;
- (NSString*)convertedTextAtIndexPath:(NSIndexPath*)indexPath;
@end
|
m文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| #import "ReactiveCocoa.h" #import "RACHttpRequestManager.h" #import "RACEXTScope.h" #import "RACModel.h"
@interface RACViewModel()
@property (nonatomic, readwrite) NSUInteger cellNums; @property (nonatomic, readwrite) BOOL canReload; @property (nonatomic, readwrite, strong) RACCommand *fetchCommand; @property (nonatomic, strong) NSArray *mItemsArr; @end
@implementation RACViewModel
- (instancetype)init { self = [super init]; if (self) { self.fetchCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) { return [[RACHttpRequestManager shareManager] fetchRequest]; }];
@weakify(self) [[self.fetchCommand.executionSignals switchToLatest] subscribeNext:^(NSArray *dataArr) { @strongify(self) if (dataArr) { self.mItemsArr = dataArr; } }];
[RACObserve(self, mItemsArr) subscribeNext:^(NSArray *x) { @strongify(self) self.cellNums = x.count; self.canReload = YES; }]; } return self; }
- (NSString *)convertedTextAtIndexPath:(NSIndexPath *)indexPath { if (!self.mItemsArr.count) { return @"0"; } RACModel *model = self.mItemsArr[indexPath.row]; NSString *str = model.title; return [str copy]; } @end
|
这是ViewModel
层,定义了一些属性供View
层绑定和监听;
同时定义了处理View
层数据转换的业务逻辑;
当收到View
层交互指令后,本层会调用Model
层去请求数据,并在信号回调中处理数据;
通过修改对外暴露的属性字段(self.canReload),通知View
层更新UI;
Model
Model实体类:
1 2 3 4 5 6 7
| @interface RACModel : NSObject @property (nonatomic, copy) NSString *title; @end
@implementation RACModel
@end
|
数据请求类.h:
1 2 3 4 5 6 7 8 9 10
| #import "RACModel.h" #import "ReactiveCocoa.h" #import "AFHTTPRequestOperationManager+RACSupport.h"
@interface RACHttpRequestManager : AFHTTPRequestOperationManager
+ (instancetype)shareManager; - (RACSignal*)fetchRequest;
@end
|
数据请求.m:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| #import "RACHttpRequestManager.h" #import "RACModel.h"
static RACHttpRequestManager *mManager;
@implementation RACHttpRequestManager
+ (instancetype)shareManager { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ mManager = [[self alloc] init]; }); return mManager; }
- (RACSignal*)fetchRequest { RACSignal *s = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { NSLog(@"++on RACSignal in fetch~"); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"++++Http responsed!"); NSMutableArray *mutArr = [NSMutableArray arrayWithCapacity:15]; for (int i = 0; i < 15; i++) { RACModel *model = [[RACModel alloc] init]; model.title = [NSString stringWithFormat:@"%d",i]; [mutArr addObject:model]; } [subscriber sendNext:mutArr]; }); return [RACDisposable disposableWithBlock:^{ }]; }]; return s; } @end
|
这是一个数据请求的工具类,是Model
层的一部分,对外提供了数据请求的信号fetchRequest
以供订阅。数据返回后被解析成RACModel
实体数组;VM
通过其内部的订阅回调收到这个数组,修改View
层绑定的对应属性,通知View
层更新UI。
- 以上实践中,
View
层持有ViewModel
并绑定其暴露的属性;
ViewModel
持有Model
层实例,并订阅了其请求数据的信号;
- 当用户点击reload按钮后,
ViewModel
层收到指令并通过Model
层请求数据;
Model
层在请求的数据返回后通过信号告知ViewModel
;
ViewModel
处理数据并通过修改View
层绑定的属性告知其更新UI;
可以看到,各层的职责也是非常清晰,通过绑定属性和订阅信号,各层处理相关业务的代码都内聚在自己的层里,这也很好的体现了“高内聚,低耦合”的架构设计要求。
4.示例2:Swift
场景:本地校验输入的登录文本格式,合规时发送请求;
要求:
- 用户名与密码均须大于6个字符;
- 用户名或密码小于6个字符时显示提示文案;
- 用户名与密码小于6字符时登录按钮显示灰色不可点击,均大于6字符时变蓝可点击;
- 点击登录按钮,发起请求并根据结果显示提示;
View与绑定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| import RxSwift import RxCocoa
class DKRxGetInController: UIViewController { @IBOutlet weak var DKNameTf: UITextField! @IBOutlet weak var DKPswTf: UITextField! @IBOutlet weak var DKNameTips: UILabel! @IBOutlet weak var DKPswTips: UILabel! @IBOutlet weak var DKGetInBtn: UIButton! let disposeBag = DisposeBag() lazy var aGetinVM:DKRxGetInViewModel = { return DKRxGetInViewModel(name: DKNameTf.rx.text.orEmpty.asObservable(), psw: DKPswTf.rx.text.orEmpty.asObservable()) }() override func viewDidLoad() { super.viewDidLoad() aGetinVM.nameObs.bind(to: DKNameTips.rx.isHidden).disposed(by: disposeBag) aGetinVM.nameObs.bind(to: DKPswTf.rx.isEnabled).disposed(by: disposeBag) aGetinVM.pswObs.bind(to: DKPswTips.rx.isHidden).disposed(by: disposeBag) let _ = aGetinVM.allObs.subscribe{ [weak self] in self?.DKGetInBtn.backgroundColor = ($0 ? UIColor.blue : UIColor.lightGray) } aGetinVM.allObs.bind(to: DKGetInBtn.rx.isEnabled).disposed(by: disposeBag) DKGetInBtn.rx.tap.subscribe{ [weak self] _ in let _ = self?.aGetinVM.fetchUserinfo(name: (self?.DKNameTf.text)!, psw: (self?.DKPswTf.text)!).subscribe { (ob) in self?.showAlert() } onError: { (error) in print(error.localizedDescription) } }.disposed(by: disposeBag) } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { view.endEditing(true) } func showAlert() { let alert = UIAlertController.init(title: "提示", message: "登录成功!", preferredStyle: .alert) let action = UIAlertAction.init(title: "Ok", style: .default, handler: nil) alert.addAction(action) present(alert, animated: true, completion: nil) } deinit { print(#file, #function) } }
|
这是View
层,它持有一个VM
实例并将输入框的文本与VM
中的属性进行双向绑定。提示文本是否显示、输入框是否允许输入、登录按钮的状态都是根据输入内容是否合规来决定的。这些校验的逻辑均在VM
中处理;点击登录按钮时通过VM
发起请求,结果返回后显示对应的提示。
ViewModel
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| import RxSwift
class DKRxGetInViewModel { let nameObs: Observable<Bool> let pswObs: Observable<Bool> let allObs: Observable<Bool> private var aModel = DKModel() init(name: Observable<String>, psw: Observable<String>) { nameObs = name.map { text in return text.count >= 6 } pswObs = psw.map { $0.count >= 6 } allObs = Observable.combineLatest(nameObs, pswObs) { $0 && $1 } } func fetchUserinfo(name:String, psw: String)->Observable<[String: Any]> { return Observable.create { [weak self] (ob) -> Disposable in let _ = self?.aModel.fetchUserinfo(name: name, psw: psw).subscribe { (dic) in ob.onNext(dic) } onError: { (error) in print(error) } return Disposables.create() } } }
|
这是一个比较简单的VM
,里面定义了校验用户名与密码的逻辑;
它持有一个Model
层实例,在给View
层的接口中通过Model
发起登录请求并回调结果。
Model
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import RxSwift import RxAlamofire
class DKModel { let disposeBag = DisposeBag() func fetchUserinfo(name:String, psw: String)->Observable<[String: Any]> { return Observable.create { [weak self] (ob) -> Disposable in let urlString = "https://www.douban.com/j/app/radio/channels" let url = URL(string:urlString)! RxAlamofire.request(.get, url) .responseJSON() .subscribe(onNext: { response in let json = response.value as! [String: Any] ob.onNext(json) }, onError: { (error) in ob.onError(error) }).disposed(by: self!.disposeBag) return Disposables.create() } } }
|
这是Model
层,其中定义了给VM
使用的登录接口,接口内发起登录请求并回调登录结果。
在Swift版示例中,各层也比较清晰,每层内部的代码高度内聚。比较示例1,它更好的实现了View
与VM
的双向绑定,即输入内容与校验结果绑定,校验结果与提示文本和登录按钮的状态绑定。
5.使用规范
- MMVM 中
V层
绑定了ViewModel
,反过来不行,ViewModel
中不能包含View
;
- MMVM 中
ViewModel
绑定了Model
层,反过来也不行;
- Controller尽量不涉及业务逻辑,让
ViewModel
去做;
- Controller是中间人,接收
View
的事件、调用ViewModel
的方法、响应VM
的变化;
- ViewModel 避免过于臃肿,否则重蹈 Controller 的覆辙变得难以维护,可拆分成子VM;
- MVVM 配合某种绑定机制时效果最好(如RAC)。
四.后记
架构一直在演进过程中,考虑到项目大小、所用语言、学习成本等因素,没有最好的架构,只有适合自己的架构。我们要根据实际需求,不断学习和调整。
相关参考:
#AppleDoc-MVC
#©sunnyxx-RAC