目的:便于开发和维护代码
M(Model):数据模型
V(View + Controller): 展示内容 + 如何展示
VM(ViewModel):视图模型,处理展示的业务逻辑,包括按钮的点击,数据的请求和解析
登录功能实现
MVC实现:
#import "ViewController.h"
#import "ReactiveCocoa.h"
#import <MBProgressHUD/MBProgressHUD.h>
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *usernameTextField;
@property (weak, nonatomic) IBOutlet UITextField *pawwordTextField;
@property (weak, nonatomic) IBOutlet UIButton *loginButton;
@end
@implementation ViewController
- (void)viewDidLoad {
// 字段校验(当用户名和密码都不为空的时候,登录按钮才可以点击)-----------------------------------
RACSignal *validateFieldSignal = [RACSignal combineLatest:@[_usernameTextField.rac_textSignal, _pawwordTextField.rac_textSignal] reduce:^id(NSString *username, NSString *password){
return @(username.length && password.length);
}];
RAC(_loginButton, enabled) = validateFieldSignal;
// 按钮点击事件------------------------------------------
RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
NSLog(@"请求接口登录...");
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"发送响应的登录数据");
[subscriber sendNext:@"发送响应的登录数据"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subscriber sendCompleted];
});
return nil;
}];
}];
[command.executionSignals.switchToLatest subscribeNext:^(id x) {
NSLog(@"接收登录的数据");
}];
// 监听命令的执行过程
[[command.executing skip:1] subscribeNext:^(id x) {
if ([x boolValue] == YES) {
NSLog(@"正在登录ing...");
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
} else {
NSLog(@"登录完成!");
[MBProgressHUD hideHUDForView:self.view animated:YES];
}
}];
[[_loginButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
NSLog(@"点击登录按钮");
[command execute:nil];
}];
}
@end
2016-12-18 11:08:32.704 ReactiveCocoa[47893:7242989] 点击登录按钮
2016-12-18 11:09:10.111 ReactiveCocoa[47893:7242989] 请求接口登录...
2016-12-18 11:09:13.871 ReactiveCocoa[47893:7242989] 正在登录ing...
2016-12-18 11:11:58.765 ReactiveCocoa[47893:7242989] 发送响应的登录数据
2016-12-18 11:12:02.722 ReactiveCocoa[47893:7242989] 接收登录的数据
2016-12-18 11:12:07.418 ReactiveCocoa[47893:7242989] 登录完成!
代码分析:
1. 程序启动时就会执行聚合信号combineLatest:reduce:,结果是登录按钮不可点击
2. 当执行到创建命令RACCommand时,参数代码块和内部代码块都不执行
3. 当执行内部信号订阅时(command.executionSignals.switchToLatest),订阅的代码块也不执行
4. 命令的执行过程也不会得到执行(因为命令还没开始执行)
5. 按钮事件rac_signalForControlEvents:也不会执行(因为没有点击按钮)
6. 当每输入用户名和密码时都会执行聚合信号,来判断按钮是否可以点击(每当UITextField的文本发生改变,内部就会产生一个信号)
7. 单击登录按钮打印@”点击登录按钮”并执行命令([command execute:nil]),紧接着会执行初始化命令时传的代码块参数signalBlock, 也就打印NSLog(@”请求接口登录…”); 一但命令执行了,那么就会监听到命令的执行过程,接着订阅命令执行过程的代码块,打印NSLog(@”正在登录ing…”); 当signalBlock执行完时会返回一个信号RACSignal,也就是说会产生一个信号,那么从信号源中就能拿到返回的信号并订阅,(command.executionSignals.switchToLatest),一但订阅就会执行命令返回的信号中的代码块,接着打印NSLog(@”发送响应的登录数据”); ,当执行sendNext的时候会发送新号,接着打印NSLog(@”接收登录的数据”);, 当执行过sendCompleted时候就会打印NSLog(@”登录完成!”);
VM:处理界面上的所有业务逻辑,每一个控制器对应一个VM,VM中最好不要包括UI
MVVM实现:
#import <Foundation/Foundation.h>
#import "ReactiveCocoa.h"
@interface LoginViewModel : NSObject
@property (strong, nonatomic) NSString *username;
@property (strong, nonatomic) NSString *password;
@property (strong, nonatomic, readonly) RACSignal *validateFieldSignal;
@property (strong, nonatomic, readonly) RACCommand *loginCommand;
@end
#import "LoginViewModel.h"
#import <MBProgressHUD/MBProgressHUD.h>
@implementation LoginViewModel
- (instancetype)init {
if (self = [super init]) {
[self setup];
}
return self;
}
- (void)setup {
_validateFieldSignal = [RACSignal combineLatest:@[RACObserve(self, username), RACObserve(self, password)] reduce:^id(NSString *username, NSString *password){
return @(username.length && password.length);
}];
_loginCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
NSLog(@"请求接口登录...");
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"发送响应的登录数据");
[subscriber sendNext:@"发送响应的登录数据"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subscriber sendCompleted];
});
return nil;
}];
}];
[_loginCommand.executionSignals.switchToLatest subscribeNext:^(id x) {
NSLog(@"接收登录的数据");
}];
// 监听命令的执行过程
[[_loginCommand.executing skip:1] subscribeNext:^(id x) {
UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
if ([x boolValue] == YES) {
NSLog(@"正在登录ing...");
[MBProgressHUD showHUDAddedTo:keyWindow animated:YES];
} else {
NSLog(@"登录完成!");
[MBProgressHUD hideHUDForView:keyWindow animated:YES];
}
}];
}
@end
#import "ViewController.h"
#import "LoginViewModel.h"
#import "ReactiveCocoa.h"
#import <MBProgressHUD/MBProgressHUD.h>
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *usernameTextField;
@property (weak, nonatomic) IBOutlet UITextField *pawwordTextField;
@property (weak, nonatomic) IBOutlet UIButton *loginButton;
@property (strong, nonatomic) LoginViewModel *loginViewModel;
@end
@implementation ViewController
- (void)viewDidLoad {
// 绑定值
RAC(self.loginViewModel, username) = _usernameTextField.rac_textSignal;
RAC(self.loginViewModel, password) = _pawwordTextField.rac_textSignal;
RAC(_loginButton, enabled) = self.loginViewModel.validateFieldSignal;
// 处理事件
[[_loginButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
NSLog(@"点击登录按钮");
[self.loginViewModel.loginCommand execute:nil];
}];
}
- (LoginViewModel *)loginViewModel {
if (_loginViewModel == nil) {
_loginViewModel = [[LoginViewModel alloc] init];
}
return _loginViewModel;
}
@end
RAC请求接口:
#import "ViewController.h"
#import "RequestViewModel.h"
#import "ReactiveCocoa.h"
@interface ViewController ()
@property (strong, nonatomic) RequestViewModel *requestViewModel;
@end
@implementation ViewController
- (void)viewDidLoad {
RACSignal *signal = [self.requestViewModel.requestCommand execute:nil];
[signal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
}
- (RequestViewModel *)requestViewModel {
if (_requestViewModel == nil) {
_requestViewModel = [[RequestViewModel alloc] init];
}
return _requestViewModel;
}
@end
#import "ViewController.h"
#import "RequestViewModel.h"
#import "ReactiveCocoa.h"
@interface ViewController ()
@property (strong, nonatomic) RequestViewModel *requestViewModel;
@end
@implementation ViewController
- (void)viewDidLoad {
RACSignal *signal = [self.requestViewModel.requestCommand execute:nil];
[signal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
}
- (RequestViewModel *)requestViewModel {
if (_requestViewModel == nil) {
_requestViewModel = [[RequestViewModel alloc] init];
}
return _requestViewModel;
}
@end
#import <Foundation/Foundation.h>
#import "ReactiveCocoa.h"
@interface RequestViewModel : NSObject
@property (strong, nonatomic) RACCommand *requestCommand;
@end
#import "RequestViewModel.h"
#import <AFNetworking/AFNetworking.h>
@implementation RequestViewModel
- (RACCommand *)requestCommand {
if (_requestCommand == nil) {
_requestCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
RACSignal *requstSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
parameters[@"q"] = @"美女";
[manager GET:@"https://api.douban.com/v2/book/search
" parameters:parameters progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[subscriber sendNext:responseObject[@"books"]];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
}];
return nil;
}];
return requstSignal;
}];
}
return _requestCommand;
}
@end