iOS MVX 设计模式漫谈

iOS MVX 设计模式漫谈

开场白

提到架构,首先要了解什么是架构?
架构依照我的了解,是归于一种编程经历的集合和总结,在经过许多人许多年的运用之后,根本稳定下来的一种形式。通常具有以下长处:适用性强,实用性强,可复用,易修正
文章将运用登录的场景分别运用 MVC、MVP、MVVM 三种形式做示例,便利进行了解与对比。

各规划形式具体分析

—MVC—

我们必定反响肯定是 MVC 。MVC便是架构中最经典的一种。

M是指事务数据, V是指用户界面, C则是控制器. 在具体的事务场景中, C作为M和V之间的连接, 担任获取输入的事务数据, 然后将处理后的数据输出到界面上做相应展现, 别的, 在数据有所更新时, C还需求及时提交相应更新到界面展现.

别的在我看来,MVC 分为两种,一种是 Model 与 View 会彼此影响,这种是经典的 MVC 架构、而别的一种便是苹果引荐的 MVC 结构,其中 Model 与 View 彼此阻隔,只经过 Controller 来更新。

在 MVC 或许 MVX 一系列的形式中,都能够把开发看作为搭积木,把 M 和 V 别离当作积木,而在 C 或许其他当地中进行拼接组合,将积木搭成想要的姿态.

经典 MVC 苹果 MVC
View 与 Model 能够互相持有 View 与 Model 都不能够持有对方
View 和 Model 能够互相修正
也能够经过 Controller 修正
只能经过 Controller 修正

层次细分

M 层分为胖瘦两种,第一种瘦 Model,只担任数据的接纳、分化和传递,能够对单个特点进行修正,也能够经过办法从字典中写入特点;别的一种是胖 Model,胖 Model 除了瘦 Model 的根本功用之外,还需求担任一部分的事务逻辑,包括但不限于:

  1. 数据的截取
  2. 时刻戳转换为正常时刻格局
  3. 添加前缀或许修正为需求的格局
  4. 数据格局的转换
  5. 对数据进行判断以及处理

V 层首要担任用户界面的组合以及界面特点等一系列与用户界面有关的功用,在经典的 MVC 中,能够经过露出一个接纳 Model 的办法来完成界面数据的修正。但在苹果的 MVC 中,因为无法引证 Model,则需求把一些需求进行改动的视图露出出来或许界说额定的数据来完成界面数据的修正。(假如是露出 UIView 的子类,则设置为 readonly;假如界说额定数据,则能够经过 get 和 set 办法修正界面数据。)

C 层的功用首要是对界面办理与事务逻辑处理,包括但不止以下内容:

  1. 对各 UI 视图进行排版、布局与适配;
  2. 完成所需求的点击事情、协议办法;
  3. 完成控制器之间的跳转;
  4. 完成网络恳求,并对成果进行处理;
  5. 一起担任 V 层与 M 层的数据的修正。

MVC 示例

在这儿以及后边的形式中,都将会经过完成视图页面来进行示例的展现。在示例中,会在一些需求难点和要点会经过注释来阐明。

在编码之前,首先需求承认一下需求完成的功用有哪些:

  1. 界面由账号 TextField、暗码 TextField、登录按钮组成
  2. 账号与暗码都需求长度检测,长度为8-16位
  3. 暗码需求格局检测(必须要有字母和数字)
  4. 登录恳求与处理

一切的根本相似,所以需求只写一次

iOS MVX 设计模式漫谈
以下是代码完成:

LoginModel.h
//
//  LoginModel.h
//  login
//
#import <Foundation/Foundation.h>
@interface LoginModel : NSObject
/// 用户名
@property (nonatomic, strong) NSString *userName;
/// 暗码
@property (nonatomic, strong) NSString *passWord;
@end
LoginModel.m
//
//  LoginModel.m
//  login
//
#import "LoginModel.h"
@implementation LoginModel
@end

在 LoginModel 中,规划成瘦 Model ,假如需求改成胖 Model ,则能够把 C 中 check 相关的代码转移过来。

LoginView.h
//
//  LoginView.h
//  login
//
#import <UIKit/UIKit.h>
@interface LoginView : UIView
/// 用户名
@property (nonatomic, strong, readonly) NSString *userName;
/// 暗码
@property (nonatomic, strong, readonly) NSString *passWord;
/// 登录回调
@property (nonatomic, copy) void (^loginBlock)();
@end
LoginView.m
//
//  LoginView.m
//  login
//
#import "LoginView.h"
@interface LoginView ()
/// 用户名输入框
@property (nonatomic, strong) UITextField *userNameTxt;
/// 暗码输入框
@property (nonatomic, strong) UITextField *passWordTxt;
/// 登录按钮
@property (nonatomic, strong) UIButton *loginBtn;
@end
@implementation LoginView
- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        [self createUI];
    }
    return self;
}
/**
 创立视图
 */
- (void)createUI {
    [self addSubview:self.userNameTxt];
    [self addSubview:self.passWordTxt];
    [self addSubview:self.loginBtn];
    [self placeSubviews];
}
/**
 子视图布局
 */
- (void)placeSubviews {
    self.userNameTxt.frame = CGRectMake(0, 0, CGRectGetWidth(self.frame), 44);
    self.passWordTxt.frame = CGRectMake(0, 50, CGRectGetWidth(self.frame), 44);
    self.loginBtn.frame = CGRectMake(20, 116, CGRectGetWidth(self.frame)-40, 44);
}
//MARK:- event response
/**
 登录点击
 */
- (void)loginEvent {
    if (self.loginBlock) {
        self.loginBlock();
    }
}
//MARK:- getter/setter
- (UITextField *)userNameTxt {
    if (!_userNameTxt) {
        _userNameTxt = [UITextField new];
        // 设置用户名根本款式
        _userNameTxt.font = [UIFont systemFontOfSize:15];
        _userNameTxt.borderStyle = UITextBorderStyleRoundedRect;
        _userNameTxt.placeholder = @"请输入用户名";
    }
    return _userNameTxt;
}
- (UITextField *)passWordTxt {
    if (!_passWordTxt) {
        _passWordTxt = [UITextField new];
        // 设置暗码根本款式
        _passWordTxt.secureTextEntry = YES;
        _passWordTxt.font = [UIFont systemFontOfSize:15];
        _passWordTxt.borderStyle = UITextBorderStyleRoundedRect;
        _passWordTxt.placeholder = @"请输入暗码";
    }
    return _passWordTxt;
}
- (UIButton *)loginBtn {
    if (!_loginBtn) {
        _loginBtn = [UIButton buttonWithType:UIButtonTypeCustom];
        // 设置按钮的根本款式
        [_loginBtn setTitle:@"登录" forState:UIControlStateNormal];
        _loginBtn.titleLabel.font = [UIFont systemFontOfSize:15];
        [_loginBtn setBackgroundColor:[UIColor redColor]];
        [_loginBtn addTarget:self action:@selector(loginEvent) forControlEvents:UIControlEventTouchUpInside];
    }
    return _loginBtn;
}
- (NSString *)userName {
    return self.userNameTxt.text;
}
- (NSString *)passWord {
    return self.passWordTxt.text;
}
@end

LoginView 中,进行了视图的处理,以及将用户名和暗码的字符露出出来,并经过 block 来获取按钮的点击事情。

LoginViewController.h
//
//  LoginViewController.h
//  login
//
#import <UIKit/UIKit.h>
@interface LoginViewController : UIViewController
@end
LoginViewController.m
//
//  LoginViewController.m
//  login
//
#import "LoginViewController.h"
#import "LoginView.h"
#import "LoginModel.h"
@interface LoginViewController ()
/// 登录视图
@property (nonatomic, strong) LoginView *loginView;
/// 登录模型
@property (nonatomic, strong) LoginModel *loginModel;
@end
@implementation LoginViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    [self createUI];
}
/**
 创立视图
 */
- (void)createUI {
    /// 这儿的布局因为是 demo,所以是写死的,没有进行过适配,子页面中的也是一样
    [self.view addSubview:self.loginView];
}
//MARK:- netWork
- (void)loginRequest {
    // 进行网络恳求 \
    成功保存账号暗码 \
    失利弹框提示
}
//MARK:- event response
- (void)check {
    if ([self passWordCheck] && [self userNameCheck]) {
        // 没问题则进行网络恳求
        [self loginRequest];
    }
    else {
        // 问题处理、提示
    }
}
- (BOOL)passWordCheck {
    // 一起包括大小写英文与数字且长度在8-16之间
    NSString *regex = @"^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{8,16}$";
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex];;
    return [predicate evaluateWithObject:self.loginModel.passWord];
}
- (BOOL)userNameCheck {
    // 包括大小写英文与数字且长度在6-20之间
    NSString *regex = @"^[a-zA-Z0-9]{8,16}$";
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex];;
    return [predicate evaluateWithObject:self.loginModel.userName];
}
//MARK:- getter/setter
- (LoginView *)loginView {
    if (!_loginView) {
        _loginView = [[LoginView alloc] initWithFrame:CGRectMake(10, CGRectGetHeight(self.view.frame)-220, CGRectGetWidth(self.view.frame)-20, 160)];
        __weak typeof(self) weakSelf = self;
        _loginView.loginBlock = ^{
            __strong typeof(self) self = weakSelf;
            self.loginModel.userName = self.loginView.userName;
            self.loginModel.passWord = self.loginView.passWord;
            [self check];
        };
    }
    return _loginView;
}
- (LoginModel *)loginModel {
    if (!_loginModel) {
        _loginModel = [LoginModel new];
    }
    return _loginModel;
}
@end

Controller 中,担任 LoginView 的布局与展现,网络恳求以及成功失利的处理,按钮点击事情的处理, View 与 Model 的交互等。


—MVP—

mvp的全称为Model-View-Presenter,Model供给数据,View担任显现,Controller/Presenter担任逻辑的处理。MVP与MVC有着一个重大的差异:在MVP中View并不直接运用Model,它们之间的通信是经过Presenter (MVC中的Controller)来进行的,一切的交互都发生在Presenter内部,而在MVC中View会直接从Model中读取数据而不是经过 Controller。

以上是经典的 MVP 形式,看起来是不是很熟悉?是不是看起来很像 MVC ?是的,在 iOS 中,引荐的 MVC 形式根本上和经典 MVP 相似(可是依旧是不一样的),而在 iOS 中,MVP 则是把 MVC 中的 Controller 进行拆分,分为 Presenter 和 Controller,所以我觉得应该叫做 MVCP 才对。

在 iOS 的 MVCP 形式中,M 是指事务数据, V 是指用户界面,P 处理事务逻辑和将数据写入界面,C 担任胶水粘合、控制器办理和视图办理。

MVP MVC
P 担任逻辑处理 C 担任逻辑处理和界面展现
在 P 中引证 V 和 M 在 C 中引证 V 和 M

层次细分

在这儿的层次细分就比 MVC 中的寒碜很多了,因为 M 层和 V 层没有发生改动,就不重复介绍了:

M 层与 MVC 中的 M 层相同;
V 层与 MVC 中的 V 层相同;
P 层将原先 C 层中的部分事务功用拆分出来(关于控制器的跳转,仍是放到 C 层中,尽量削减耦合),一起 P 层引证了 V 层和 M 层,而且 V 层和 M 层的修正与完成经过 P 层进行处理;
C 层在 P 层把功用拆分出来之后,只剩下了部分功用,包括:

  1. 胶水功用,P 层、M 层、V 层的链接都是经过 C 层进行
  2. 控制器之间的交互
  3. 视图办理

iOS MVX 设计模式漫谈

MVP 示例

LoginPresenter.h
//
//  LoginPresenter.h
//  login
//
#import <UIKit/UIKit.h>
@class LoginView,LoginModel;
@interface LoginPresenter : NSObject
/// 登录视图
@property (nonatomic, strong) LoginView *loginView;
/// 登录模型
@property (nonatomic, strong) LoginModel *loginModel;
/// 登录失利回调
@property (nonatomic, copy) void (^loginSuccessBlock)();
/// 登录成功回调
@property (nonatomic, copy) void (^loginFailBlock)();
@end
LoginPresenter.m
//
//  LoginPresenter.m
//  login
//
#import "LoginPresenter.h"
#import "LoginView.h"
#import "LoginModel.h"
@implementation LoginPresenter
//MARK:- netWork
- (void)loginRequest {
    // 进行网络恳求 \
    成功保存账号暗码 \
    失利弹框提示
    if (/* success && */self.loginSuccessBlock) {
        self.loginSuccessBlock();
    }
    else if (/* fail && */self.loginFailBlock) {
        self.loginFailBlock();
    }
}
//MARK:- event response
- (void)check {
    if ([self passWordCheck] && [self userNameCheck]) {
        // 没问题则进行网络恳求
        [self loginRequest];
    }
    else {
        // 问题处理、提示
    }
}
- (BOOL)passWordCheck {
    // 一起包括大小写英文与数字且长度在8-16之间
    NSString *regex = @"^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{8,16}$";
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex];;
    return [predicate evaluateWithObject:self.loginModel.passWord];
}
- (BOOL)userNameCheck {
    // 包括大小写英文与数字且长度在6-20之间
    NSString *regex = @"^[a-zA-Z0-9]{8,16}$";
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex];;
    return [predicate evaluateWithObject:self.loginModel.userName];
}
//MARK:- getter/setter
- (void)setLoginView:(LoginView *)loginView {
    _loginView = loginView;
    __weak typeof(self) weakSelf = self;
    _loginView.loginBlock = ^{
        __strong typeof(self) self = weakSelf;
        self.loginModel.userName = self.loginView.userName;
        self.loginModel.passWord = self.loginView.passWord;
        [self check];
    };
}
@end
LoginViewController.m
//
//  LoginViewController.m
//  login
//
#import "LoginViewController.h"
#import "LoginView.h"
#import "LoginModel.h"
#import "LoginPresenter.h"
@interface LoginViewController ()
/// 登录视图
@property (nonatomic, strong) LoginView *loginView;
/// 登录模型
@property (nonatomic, strong) LoginModel *loginModel;
@property (nonatomic, strong) LoginPresenter *loginPresenter;
@end
@implementation LoginViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    [self createUI];
}
/**
 创立视图
 */
- (void)createUI {
    /// 这儿的布局因为是 demo,所以是写死的,没有进行过适配,子页面中的也是一样
    [self.view addSubview:self.loginView];
    self.loginPresenter.loginView = self.loginView;
    self.loginPresenter.loginModel = self.loginModel;
}
//MARK:- private method
/**
 登录成功
 */
- (void)loginSuccess {
}
/**
 登录失利
 */
- (void)loginFail {
}
//MARK:- getter/setter
- (LoginView *)loginView {
    if (!_loginView) {
        _loginView = [[LoginView alloc] initWithFrame:CGRectMake(10, CGRectGetHeight(self.view.frame)-220, CGRectGetWidth(self.view.frame)-20, 160)];
    }
    return _loginView;
}
- (LoginModel *)loginModel {
    if (!_loginModel) {
        _loginModel = [LoginModel new];
    }
    return _loginModel;
}
- (LoginPresenter *)loginPresenter {
    if (!_loginPresenter) {
        _loginPresenter = [LoginPresenter new];
        __weak typeof(self) weakSelf = self;
        _loginPresenter.loginSuccessBlock = ^{
            __strong typeof(self) self = weakSelf;
            [self loginSuccess];
        };
        _loginPresenter.loginFailBlock = ^{
            __strong typeof(self) self = weakSelf;
            [self loginFail];
        };
    }
    return _loginPresenter;
}
@end

MVP 和 MVC 比较,把事务逻辑单独提取到 P 层处理,便利问题的定位、测试和修正,但一起需求把控制器或许视图控制部分功用进行回调(因为是控制器的功用),可是缺陷便是耦合度比较大,根本无法进行复用,其次便是会多出一个文件,假如一切的都转为 MVP ,则平均会多上1/4的文件,不便利办理。

—MVVM—

MVVM是Model-View-ViewModel的简写,MVVM(Model-View-ViewModel)结构的由来便是MVP(Model-View-Presenter)形式与WPF结合的应用方法时开展演变过来的一种新式架构结构。它立足于原有MVP结构而且把WPF的新特性糅合进去,以应对客户日益复杂的需求改动。

MVVM 便是 MVP 的升级版本,假如在之前现已对 MVP 有必定的了解,那么了解 MVVM 的进程将会变得简略而便利。一起,跟 MVP 同理,MVVM 在 iOS 中,应该被称为 MVCVM。究竟控制器是你永远都摆脱不了的。

在 iOS 的 MVCVM 形式中,M 是指事务数据, V 是指用户界面,VM 处理事务逻辑和将数据写入界面,C 担任胶水粘合、控制器办理和视图办理。

MVVM MVP
VM 担任事务数据的绑定和事务逻辑的处理 P 担任逻辑处理
V 层引证 VM,VM 引证 M层 P 层一起引证 V 层和 M 层

层次细分

在这儿的层次细分就跟 MVP 中的差不多,首要仍是引证和绑定的差异:

M 层与 MVC 中的 M 层相同;
V 层在 MVC 中的 V 层的基础上,加上 VM 层的引证,完成由 VM 的数据绑定导致的特点修正;
VM 层与 MVP 中的 P 层相相似,首要差异在于:

  1. VM 层只引证 M 层,一起对 M 层的数据进行绑定;
  2. V 层和 C 层引证 VM 层,完成对行的处理;
  3. 事务逻辑的处理放在 VM 中,一起放出需求其他层合作的回调(V 层或许 C 层)
  4. 有必要的情况下,能够进行双向绑定

C 层与 MVP 中的 C 层相似,只不过把 P 层换成 VM 层,并把改动了引证方法。

MVVM 示例

特别阐明:为了更便利的运用 MVVM ,我导入了 RAC 结构

iOS MVX 设计模式漫谈

LoginView.h
//
//  LoginView.h
//  login
//
#import <UIKit/UIKit.h>
#import "LoginViewModel.h"
@interface LoginView : UIView
//MARK:- method
- (instancetype)initWithFrame:(CGRect)frame
                    ViewModel:(LoginViewModel *)viewModel;
@end
LoginView.m
//
//  LoginView.m
//  login
//
#import "LoginView.h"
@interface LoginView ()
/// 用户名输入框
@property (nonatomic, strong) UITextField *userNameTxt;
/// 暗码输入框
@property (nonatomic, strong) UITextField *passWordTxt;
/// 登录按钮
@property (nonatomic, strong) UIButton *loginBtn;
/// login viewModel
@property (nonatomic, strong) LoginViewModel *viewModel;
@end
@implementation LoginView
- (instancetype)initWithFrame:(CGRect)frame
                    ViewModel:(LoginViewModel *)viewModel {
    if (self = [super initWithFrame:frame]) {
        self.viewModel = viewModel;
        [self createUI];
    }
    return self;
}
/**
 创立视图
 */
- (void)createUI {
    [self addSubview:self.userNameTxt];
    [self addSubview:self.passWordTxt];
    [self addSubview:self.loginBtn];
    [self bindViewModel];
}
//MARK:- VMBind
/**
 绑定VM
 */
- (void)bindViewModel {
    /// 树立 model -> view 的绑定
    [self.viewModel.userNameChange subscribeNext:^(id x) {
        self.userNameTxt.text = x;
    }];
    [self.viewModel.passWordChange subscribeNext:^(id x) {
        self.passWordTxt.text = x;
    }];
    /// 树立 view -> model 的绑定
    self.viewModel.userNameSignal = [self.userNameTxt rac_textSignal];
    self.viewModel.passWordSignal = [self.passWordTxt rac_textSignal];
    /// 登录点击事情
    [[self.loginBtn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
        [self endEditing:YES];
        [self.viewModel.loginSubject sendNext:nil];
    }];
}
//MARK:- getter/setter
- (UITextField *)userNameTxt {
    if (!_userNameTxt) {
        _userNameTxt = [UITextField new];
        // 设置用户名根本款式
        _userNameTxt.font = [UIFont systemFontOfSize:15];
        _userNameTxt.borderStyle = UITextBorderStyleRoundedRect;
        _userNameTxt.placeholder = @"请输入用户名";
    }
    return _userNameTxt;
}
- (UITextField *)passWordTxt {
    if (!_passWordTxt) {
        _passWordTxt = [UITextField new];
        // 设置暗码根本款式
        _passWordTxt.secureTextEntry = YES;
        _passWordTxt.font = [UIFont systemFontOfSize:15];
        _passWordTxt.borderStyle = UITextBorderStyleRoundedRect;
        _passWordTxt.placeholder = @"请输入暗码";
    }
    return _passWordTxt;
}
- (UIButton *)loginBtn {
    if (!_loginBtn) {
        _loginBtn = [UIButton buttonWithType:UIButtonTypeCustom];
        // 设置按钮的根本款式
        [_loginBtn setTitle:@"登录" forState:UIControlStateNormal];
        _loginBtn.titleLabel.font = [UIFont systemFontOfSize:15];
        [_loginBtn setBackgroundColor:[UIColor redColor]];
    }
    return _loginBtn;
}
@end
LoginViewModel.h
//
//  LoginViewModel.h
//  login
//
#import <Foundation/Foundation.h>
#import <ReactiveCocoa/ReactiveCocoa.h>
@class LoginModel;
@interface LoginViewModel : NSObject
/// model 用户名改动
@property (nonatomic, strong) RACSubject  *userNameChange;
/// model 暗码改动
@property (nonatomic, strong) RACSubject  *passWordChange;
/// 用户名改动
@property (nonatomic, strong) RACSignal  *userNameSignal;
/// 暗码改动
@property (nonatomic, strong) RACSignal  *passWordSignal;
/// 登录事情
@property (nonatomic, strong) RACSubject  *loginSubject;
/// 登录成果
@property (nonatomic, strong) RACSubject  *loginResultSubject;
#pragma mark- method
/**
 初始化
 @param loginModel 登录 model
 */
- (instancetype)initWithModel:(LoginModel *)loginModel;
@end
LoginViewModel.m
//
//  LoginViewModel.m
//  login
//
#import "LoginViewModel.h"
#import "LoginModel.h"
@interface LoginViewModel ()
/// 登录 model
@property (nonatomic, strong) LoginModel  *loginModel;
@end
@implementation LoginViewModel
- (instancetype)initWithModel:(LoginModel *)loginModel {
    if (self = [super init]) {
        self.loginModel = loginModel;
        [RACObserve(self.loginModel, userName) subscribeNext:^(id x) {
            [self.userNameChange sendNext:x];
        }];
        [RACObserve(self.loginModel, passWord) subscribeNext:^(id x) {
            [self.passWordChange sendNext:x];
        }];
    }
    return self;
}
//MARK:- netWork
- (void)loginRequest {
    // 进行网络恳求 \
    成功保存账号暗码 \
    失利弹框提示
    if (/* success && */self.loginResultSubject) {
        [self.loginResultSubject sendNext:@(YES)];
    }
    else if (/* fail && */self.loginResultSubject) {
        [self.loginResultSubject sendNext:@(NO)];
    }
}
//MARK:- event response
- (void)check {
    if ([self passWordCheck] && [self userNameCheck]) {
        // 没问题则进行网络恳求
        [self loginRequest];
    }
    else {
        // 问题处理、提示
    }
}
- (BOOL)passWordCheck {
    // 一起包括大小写英文与数字且长度在8-16之间
    NSString *regex = @"^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{8,16}$";
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex];;
    return [predicate evaluateWithObject:self.loginModel.passWord];
}
- (BOOL)userNameCheck {
    // 包括大小写英文与数字且长度在6-20之间
    NSString *regex = @"^[a-zA-Z0-9]{8,16}$";
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex];;
    return [predicate evaluateWithObject:self.loginModel.userName];
}
#pragma mark- getter/setter
- (RACSubject *)loginSubject {
    if (!_loginSubject) {
        _loginSubject = [RACSubject subject];
    }
    return _loginSubject;
}
- (RACSubject *)userNameChange {
    if (!_userNameChange) {
        _userNameChange = [RACSubject subject];
    }
    return _userNameChange;
}
- (RACSubject *)passWordChange {
    if (!_passWordChange) {
        _passWordChange = [RACSubject subject];
    }
    return _passWordChange;
}
- (RACSubject *)loginResultSubject {
    if (!_loginResultSubject) {
        _loginResultSubject = [RACSubject subject];
    }
    return _loginResultSubject;
}
- (void)setUserNameSignal:(RACSignal *)userNameSignal {
    _userNameSignal = userNameSignal;
    [self.userNameSignal subscribeNext:^(id x) {
        self.loginModel.userName = x;
    }];
}
- (void)setPassWordSignal:(RACSignal *)passWordSignal {
    _passWordSignal = passWordSignal;
    [self.passWordSignal subscribeNext:^(id x) {
        self.loginModel.passWord = x;
    }];
}
@end
LoginViewCOntroller.m
//
//  LoginViewCOntroller.m
//  login
//
#import "LoginViewCOntroller.h"
#import "LoginView.h"
#import "LoginModel.h"
#import "LoginViewModel.h"
@interface LoginViewCOntroller ()
/// 登录视图
@property (nonatomic, strong) LoginView *loginView;
/// 登录模型
@property (nonatomic, strong) LoginModel *loginModel;
/// 登录 viewModel
@property (nonatomic, strong) LoginViewModel *loginViewModel;
@end
@implementation LoginViewCOntroller
- (void)viewDidLoad {
    [super viewDidLoad];
    [self createUI];
}
/**
 创立视图
 */
- (void)createUI {
    /// 这儿的布局因为是 demo,所以是写死的,没有进行过适配,子页面中的也是一样
    [self.view addSubview:self.loginView];
}
//MARK:- private method
/**
 登录成功
 */
- (void)loginSuccess {
}
/**
 登录失利
 */
- (void)loginFail {
}
//MARK:- getter/setter
- (LoginView *)loginView {
    if (!_loginView) {
        _loginView = [[LoginView alloc] initWithFrame:CGRectMake(20, 60, self.view.frame.size.width-40, 160) ViewModel:self.loginViewModel];
    }
    return _loginView;
}
- (LoginViewModel *)loginViewModel {
    if (!_loginViewModel) {
        _loginViewModel = [[LoginViewModel alloc] initWithModel:self.loginModel];
        @weakify(self);
        [_loginViewModel.loginResultSubject subscribeNext:^(id x) {
            @strongify(self);
            if ([x boolValue]) {
                [self loginSuccess];
            }
            else {
                [self loginFail];
            }
        }];
    }
    return _loginViewModel;
}
- (LoginModel *)loginModel {
    if (!_loginModel) {
        _loginModel = [LoginModel new];
    }
    return _loginModel;
}
@end

MVVM 跟 MVP 比较,更加的独立,只担任逻辑,能够进行专门的开发和处理,不需求视图的合作,一起具有可重用的能力,能够让多个视图一起运用这段逻辑,一起允许进行数据的绑定和监听。简略说来便是 MVP 该有的长处都有,一起更上一层楼,可是 MVP 的多文件缺陷也一起保存下来了。


结束

MVX 系列的规划形式就暂时到此,查看代码与类之间的结构能够更便利的了解文章中所想表达的内容。当然,这篇文章归所以一篇闲谈,也便是表述我自身所了解的、所了解的 MVX 规划形式,有缺乏和过错的当地期望能够纠正。


  • 我正在参加技术社区创作者签约方案招募活动,点击链接报名投稿。