上一篇文章 以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;
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
结果:
主要是LPCommand的使用 (不足之处希望指出,一起探讨,一起学习)