IOS - MVVM Without ReactiveCocoa(Block方式实现数据绑定)

上一篇文章 以MVVM+RAC实现了一个简单的用户登录页面,但是有时候基于多方面考虑 我们不会集成RAC这个重量级三方库,但是MVVM没有RAC这样的利器做数据绑定相对比较头疼,但是也有很多替代方案,蘑菇街大神limboy就有一篇关于MVVM Without ReactiveCocoa的文章,本着学习的心态写了一个 LPCommand 来代替 RACCommand的职能,

PLCommand.h

#import 
/**
 *  成功时的回调block
 *
 *  @param error 错误
 *  @param content 内容
 */
typedef void(^PLCommandCompletionBlock)(id error,id content);
/**
 *  执行的block
 *
 *  @param input             输入
 *  @param completionHandler 完成结果
 */
typedef void(^PLCommandConsumeBlock)(id input,PLCommandCompletionBlock completionHandler);

/**
 *  预执行的block
 */
typedef void(^PLCommandPrepareBlock)(void);

/**
 *  取消的block
 */
typedef void(^PLCommandCancelBlock)(void);


/**
 *  封装逻辑
 */
@interface PLCommand : NSObject

/**
 *   初始化
 *
 *  @param consumeHandler 处理事件
 *
 *  @return 对象
 */
- (instancetype)initWithConsumeHandler:(PLCommandConsumeBlock)consumeHandler;
/**
 *  带取消操作
 *
 *  @param consumeHandler 处理事件
 *  @param cancelHandler 取消事件
 *
 *  @return 对象
 */
- (instancetype)initWithConsumeHandler:(PLCommandConsumeBlock)consumeHandler cancelHandler:(PLCommandCancelBlock)cancelHandler;
/**
 *  发起执行
 *
 *  @param input 数据
 */
- (void)execute:(id)input;

/**
 发布执行结果

 @param input 数据
 @param error 错误信息
 */
- (void)sendNext:(id)input error:(NSError *)error;


/**
 监听执行结果

 @param prepareblock 预执行 execute触发时候回调
 @param completionHandler 执行完毕 sendNext触发时回调
 */
- (void)prepare:(PLCommandPrepareBlock)prepareblock subscribeNext:(PLCommandCompletionBlock)completionHandler;

/**
 *  取消
 */
- (void)cancel;

@end

PLCommand.m

#import "PLCommand.h"

@interface PLCommand()

@property (nonatomic,copy) PLCommandConsumeBlock consumeHandler;/**< 执行的block*/
@property (nonatomic,copy) PLCommandCancelBlock cancelHandler;/**< 取消*/
@property (nonatomic,copy) PLCommandCompletionBlock completionHandler;/**< 成功*/

@property (nonatomic,copy) PLCommandPrepareBlock prepareHandler;/**< 预执行block*/
@property (nonatomic,copy) PLCommandCompletionBlock subscribeNextHandler;/**< 订阅结果block*/

@end

@implementation PLCommand

- (instancetype)initWithConsumeHandler:(PLCommandConsumeBlock)consumeHandler {
    self = [super init];
    if (!self) {
        return nil;
    }
    _consumeHandler = consumeHandler;
    return self;
}

- (instancetype)initWithConsumeHandler:(PLCommandConsumeBlock)consumeHandler cancelHandler:(PLCommandCancelBlock)cancelHandler {
    self = [super init];
    if (!self) {
        return nil;
    }
    _consumeHandler = consumeHandler;
    _cancelHandler = cancelHandler;
    return self;
}

- (void)execute:(id)input {
    if (self.consumeHandler) {
        self.consumeHandler(input,self.completionHandler);
    }
    if (self.prepareHandler) {
        self.prepareHandler();
    }
}

- (void)sendNext:(id)input error:(NSError *)error {
    if (self.subscribeNextHandler) {
        self.subscribeNextHandler(error, input);
    }
}

- (void)prepare:(PLCommandPrepareBlock)prepareblock subscribeNext:(PLCommandCompletionBlock)completionHandler {
    self.prepareHandler = prepareblock;
    self.subscribeNextHandler = completionHandler;
}

- (void)cancel {
    //取消请求
    if (self.cancelHandler) {
        self.cancelHandler();
    }
}

@end

执行流程

/**
 *  发起执行
 *
 *  @param input 数据
 */
- (void)execute:(id)input;

execute:会触发initWithConsumeHandler:里面consumeHandler 内部接收input参数

/**
 *   初始化
 *
 *  @param consumeHandler 处理事件
 *
 *  @return 对象
 */
- (instancetype)initWithConsumeHandler:(PLCommandConsumeBlock)consumeHandler;
/**
 发布执行结果

 @param input 数据
 @param error 错误信息
 */
- (void)sendNext:(id)input error:(NSError *)error;

sendNext:会触发subscribeNext:回调(回调input和error参数)

/**
 监听执行结果

 @param prepareblock 预执行 execute触发时候回调
 @param completionHandler 执行完毕 sendNext触发时回调
 */
- (void)prepare:(PLCommandPrepareBlock)prepareblock subscribeNext:(PLCommandCompletionBlock)completionHandler;

配合登录demo

Model

@interface LoginModel : NSObject

@property (strong, nonatomic) NSString *userId;
@property (strong, nonatomic) NSString *displayName;

@end

ViewModel

#import "LoginModel.h"
#import "PLCommand.h"

@interface LoginViewModel : NSObject

@property (nonatomic,copy) NSString *account;
@property (nonatomic,copy) NSString *pwd;

@property (nonatomic,strong) NSError *error;
@property (nonatomic,strong) LoginModel *loginModel;

//文本改变信息传递
@property (nonatomic,strong) PLCommand *accountChangeCmd;
@property (nonatomic,strong) PLCommand *pwdChangeCmd;

//按钮可用信息传递
@property (nonatomic,strong) PLCommand *loginBtnEnableSDCmd;
//登录数据信息传递
@property (nonatomic,strong) PLCommand *loginActionSDCmd;

@end

1.因为没有RAC这个观察者所以 V的文本的变更只能也通过cmd的形式来告诉VM,

2.登录按钮的可用状态通过cmd来传递状态

3.V通过cmd告诉VM发起登录请求操作 网络数据回来之后 VM通过同一个cmd把结果回调给V

@implementation LoginViewModel
- (instancetype)init{
    if (self = [super init]) {
        //双向绑定
        [self initCommand];
        [self initSubscribe];
    }
    return self;
}

- (void)initSubscribe {
    self.accountChangeCmd = [[PLCommand alloc] initWithConsumeHandler:^(id input, PLCommandCompletionBlock completionHandler) {
        self.account = input;
        [self checkSubmitEnable];
    }];
    self.pwdChangeCmd = [[PLCommand alloc] initWithConsumeHandler:^(id input, PLCommandCompletionBlock completionHandler) {
        self.pwd = input;
        [self checkSubmitEnable];
    }];
}

- (void)initCommand {
    self.loginBtnEnableSDCmd = [[PLCommand alloc] initWithConsumeHandler:^(id input, PLCommandCompletionBlock completionHandler) {
        [self racForSubmitEnable];
    }];
    self.loginActionSDCmd = [[PLCommand alloc] initWithConsumeHandler:^(id input, PLCommandCompletionBlock completionHandler) {
        [self racForlogin];
    }];
}

- (void)checkSubmitEnable {
    [self.loginBtnEnableSDCmd execute:nil];
}

//校验登录按钮的状态
- (void)racForSubmitEnable {
    Boolean enable = self.account.length == 3 & self.pwd.length == 3;
    [self.loginBtnEnableSDCmd sendNext:@(enable) error:nil];
}

//登录操作
- (void)racForlogin {
    //模拟网络请求
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        LoginModel *result = [LoginModel new];
        if ([self.account isEqualToString:@"123"] && [self.pwd isEqualToString:@"123"]) {
            //模拟请求结果
            result.userId = [NSString stringWithFormat:@"%@%@",self.account,self.pwd];
            result.displayName = result.userId;
            self.error = nil;
        } else {
            //模拟错误
            self.error = [NSError errorWithDomain:@"-1" code:-1 userInfo:@{@"des":@"账号密码错误"}];
        }
        self.loginModel = result;
        //结果发送给监听方
        [self.loginActionSDCmd sendNext:self error:self.error];
    });
}

@end

以上都是通过cmd来传递信息 V持有VM ,VM持有M

ViewController

#import "ViewController.h"
#import 
#import "LoginViewModel.h"
#import 
@interface ViewController ()

@property (weak, nonatomic) IBOutlet UITextField *txtAccount;
@property (weak, nonatomic) IBOutlet UITextField *txtPwd;
@property (weak, nonatomic) IBOutlet UIButton *btnSubmit;
@property (weak, nonatomic) IBOutlet UILabel *lblResult;

@property (strong,nonatomic) LoginViewModel *viewModel;

- (IBAction)touchAction:(id)sender;

@end


@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self setupUI];
    //双向绑定
    [self initCommand];
    [self initSubscribe];
}

- (void)setupUI {
    self.btnSubmit.enabled = NO;
    [self.btnSubmit setBackgroundImage:[self createImageWithColor:[UIColor blueColor]] forState:UIControlStateNormal];
    [self.btnSubmit setBackgroundImage:[self createImageWithColor:[UIColor grayColor]] forState:UIControlStateDisabled];
}

-(UIImage*)createImageWithColor:(UIColor*) color{
    CGRect rect=CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
    UIGraphicsBeginImageContext(rect.size);
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetFillColorWithColor(context, [color CGColor]);
    CGContextFillRect(context, rect);
    UIImage *theImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return theImage;
}

- (void)initCommand {
    [self.txtAccount addTarget:self action:@selector(txtAccountChange) forControlEvents:UIControlEventEditingChanged];
    [self.txtPwd addTarget:self action:@selector(txtPwdChange) forControlEvents:UIControlEventEditingChanged];
}

- (void)txtAccountChange {
    [self.viewModel.accountChangeCmd execute:self.txtAccount.text];
}

- (void)txtPwdChange {
    [self.viewModel.pwdChangeCmd execute:self.txtPwd.text];
}

- (void)initSubscribe {
    [self.viewModel.loginBtnEnableSDCmd prepare:nil subscribeNext:^(id error, id content) {
        NSNumber *value = content;
        self.btnSubmit.enabled = [value boolValue];
        self.lblResult.text = @"";
    }];
    
    [self.viewModel.loginActionSDCmd prepare:^{
        [SVProgressHUD showWithStatus:@"正在请求"];
    } subscribeNext:^(id error, id content) {
        LoginViewModel *loginViewModel = content;
        if (!loginViewModel) {
            [SVProgressHUD showInfoWithStatus:@"登录失败"];
            return ;
        }
        if (error) {
            //错误信息自行处理
            self.lblResult.text = [NSString stringWithFormat:@"%@",loginViewModel.error.userInfo[@"des"]];
            [SVProgressHUD showInfoWithStatus:@"登录失败"];
        } else {
            self.lblResult.text = loginViewModel.loginModel.userId;
            [SVProgressHUD showSuccessWithStatus:@"登录成功"];
        }
    }];
}

- (IBAction)touchAction:(id)sender {
    //以cmd的形式进行通信 告诉VM发起login请求(结果上面已经有做监听)
    [self.viewModel.loginActionSDCmd execute:nil];
}

-(LoginViewModel *)viewModel{
    if (_viewModel == nil) {
        _viewModel = [LoginViewModel new];
    }
    return _viewModel;
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}



@end

结果:

IOS - MVVM Without ReactiveCocoa(Block方式实现数据绑定)_第1张图片

 

 

 

 

 

 

 

 

 

 

主要是LPCommand的使用 (不足之处希望指出,一起探讨,一起学习)

 

 

你可能感兴趣的:(代码笔记,随笔)