目的:便于开发和维护代码

M(Model):数据模型
V(View + Controller): 展示内容 + 如何展示
VM(ViewModel):视图模型,处理展示的业务逻辑,包括按钮的点击,数据的请求和解析

登录功能实现

MVC实现:

MVVM_数据

#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