利用RAC制作一个登录界面小Demo
2017-05-04 本文已影响968人
小冰山口
这里有一篇关于MVVM, 我觉得最好的解释说明:
对于MVC架构, 即我们熟知的Model-View-Controller架构, 如下图所示:
MVC架构M层请求数据, V层展示数据, C层处理逻辑. 但在实际开发过程中, C层和V层是偶联在一起的, 就形成了这种格局:
MVC架构C层与M层交互, 这就往往会造成C层的逻辑过多, 代码臃肿.
这个时候, 就产生了MVVM架构, 即Model-View-ViewModel架构.即在C层和M层中间加了一个ViewModel层, ViewModel就是用来为C层瘦身的. 它的出现, 大大减少了控制器中的逻辑处理. 是C层变得轻巧.
MVVM架构RAC在信号传递方面有着得天独厚的优势, 所以RAC+MVVM架构可以说是天作之合. 今天利用RAC写了一个登录界面, 把C层的逻辑处理抽出来放到了ViewModel层 :
登录界面的架构C层拥有V层和VM层:
C层拥有V层和VM层V层和VM层的数据交互放在C层处理:
V层和VM层的数据交互放在C层处理这个小Demo没有Model, Model现在不直接与C层交互, 而是通过ViewModel.这样一来, 各个类之间各行其是:
-
需求1: 账号和密码都填时, 登录按钮才可以点击:
步骤1: View层的数据通过C层给到VM层:
- (void)loginButtonEnable {
RAC(self.loginViewModel, username) = _loginView.usernameTextField.rac_textSignal;
RAC(self.loginViewModel, password) = _loginView.passwordTextField.rac_textSignal;
}
步骤2: VM层进行逻辑处理:
self.loginButtonEnableSignal = [RACSignal combineLatest:@[RACObserve(self, username), RACObserve(self, password)] reduce:^id(NSString *username, NSString *password){
return @(username.length && password.length);
}];
以上代码会返回一个包装了的BOOL值
步骤3: View层显示数据:
RAC(self.loginView.loginButton, enabled) = _loginViewModel.loginButtonEnableSignal;
给button的enabled属性赋值, 告知按钮是否可以点击.
-
需求2: 响应按钮的点击事件, 当账号密码正确时提示正确, 并跳转控制器, 当账号和密码错误时, 提示错误.
步骤1: View层的按钮响应点击事件, 并执行VM层的命令:
[[_loginView.loginButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
[self.loginViewModel.loginCommad execute:nil];
}];
步骤2: VM层创建命令:
self.loginCommad = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id input) {
return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://120.25.226.186:32812/login"]];
request.HTTPMethod = @"POST";
NSString *paramString = [NSString stringWithFormat:@"username=%@&pwd=%@&type=JSON", self.username, self.password];
NSData *paramData = [paramString dataUsingEncoding:NSUTF8StringEncoding];
request.HTTPBody = paramData;
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSDictionary *resultDictionary = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:NULL];
[subscriber sendNext:resultDictionary];
/****************** -------- 发送完成这一步很重要, 不然后面的无法信号无法执行 -------- ******************/
[subscriber sendCompleted];
}] resume];
return nil;
}];
}];
- 我们会将处理网络请求的逻辑写在命令中.
- 创建命令会填入一个block参数, 这个block参数会返回一个信号,在这个信号中, 我们会将请求的网络数据发送出去.
- 我们需要订阅这个信号才能拿到这个网络数据, 如何拿到呢?
- 在命令中有一个信号源属性
executionSignals
- 信号源还有一个属性
switchToLatest
让我们可以拿到block中返回的信号,然后再订阅这个信号, 我们就可以拿到服务器返回的数据, 然后进行登录逻辑的处理:
- 在命令中有一个信号源属性
[self.loginCommad.executionSignals.switchToLatest subscribeNext:^(NSDictionary *x) {
if ([x.allKeys.lastObject isEqualToString:@"success"]) {
[SVProgressHUD showSuccessWithStatus:@"登录成功"];
[SVProgressHUD dismissWithDelay:1 completion:^{
YFFirstPageViewController *firstPageVC = [[YFFirstPageViewController alloc] init];
[UIView animateWithDuration:1 animations:^{
[UIApplication sharedApplication].keyWindow.rootViewController = firstPageVC;
}];
}];
}else {
[SVProgressHUD showErrorWithStatus:@"登录失败"];
[SVProgressHUD dismissWithDelay:1];
}
}];
- 还可以监控命令的执行过程:
[[self.loginCommad.executing skip:1] subscribeNext:^(NSNumber * _Nullable x) {
if ([x boolValue]) {
[SVProgressHUD showWithStatus:@"正在登录中"];
}
}];
以上这些逻辑都是在VM层处理的.
在V层中, 我们只需要处理控件的布局即可, 而不需要处理任何逻辑事件:
控件的布局这就是整个Demo的架构, 实现逻辑和代码, 效果如下:
Demo运行效果