MVVM+RAC简单使用教程

MVVM+RAC简单使用教程


既然是简单使用那就不讲那些概念了。什么是MVC、MVP、MVVM这些就忽略啦!很多大神写了很多,有关他们的优缺点都写的非常的好,我就没必要再写一遍了。
直接开始写怎么开始一个 MVVM+RAC 的教程了。
我喜欢在写代码的时候先写思路,这样就顺便把注释也写了。
写Demo和下厨是一样一样的,得有先材料。
那么写这个也要准备一下材料:

1. 导入ReactiveCocoa框架。

这里没有两个版本一个OC和一个Swift版本。如果你的项目是OC的你没必要在把Swift的下载下来,而且还要配置不然工程会报错。

Swift:

use_frameworks!
    target 'Target名称' do
    pod 'ReactiveCocoa', '5.0.0-alpha.3' 
end

OC:

use_frameworks!
    target 'Target名称' do
    pod 'ReactiveObjC', '~> 2.1.0' 
 end

我用的是OC版本,网络我用到了AFNetworking

   use_frameworks!
    target 'Target名称' do
    pod 'ReactiveObjC', '~> 2.1.0' 
    pod 'AFNetworking', '~> 3.0'
 end 

2. New一个MVVM项目 我们就由一个简单的登录业务模块开始,然后跳转到一个网络加载的数据列表页,点击Cell跳转页面。这样一个小的Demo,也比较容易去理解整个MVVM和RAC的使用。

新建工程后:

2.1 新建一个MVVM-prefix.pch 文件

    #import 
    #import 

因为是Demo所以就这么写了。

2.2新建一个 model 用来当信号里的属性 用来监听信号

 #import 

@interface AccountModel : NSObject
@property(nonatomic,strong)NSString *userName;
@property(nonatomic,strong)NSString *pwd;
@end
2.3 新建一个 ViewModel
#import 
#import "AccountModel.h"
@interface LoginViewModel : NSObject
@property(nonatomic,strong)AccountModel *account;
@property(nonatomic,strong)RACSignal *listeningSignal;
@property(nonatomic,strong)RACSubject *btnColorSubject; 
@property(nonatomic,strong)RACCommand *loginCommand;
@property(nonatomic,strong)UINavigationController *nav;
@end


#import "LoginViewModel.h"
#import "ListViewController.h"
@implementation LoginViewModel
-(AccountModel*)account{
    if (!_account) {
        _account = [[AccountModel alloc]init];
    }
    return _account;
}

-(instancetype)init{
    if (self=[super init]) {
        [self initalBind];
    }
    return self;
}

-(void)initalBind{
    ////创建信号 通过combinelatest 讲两个型号组合一起 通过一个宏RACObserve 来观察Account 里面的 属性的变化
    _listeningSignal = [RACSignal combineLatest:@[RACObserve(self.account, userName),RACObserve(self.account, pwd)] reduce:^id(NSString *account,NSString *pwd){
        ////这里就判断 发送哪个信号出去 用来改变颜色
//        if (account.length && pwd.length) {
//            [self.btnColorSubject sendNext:@1];
//        } else {
//             [self.btnColorSubject sendNext:@2];
//        }
        return @(account.length&&pwd.length);
    }];
    ////创建一个RACCommand 事件
    // 1.signalBlock必须要返回一个信号,不能传nil.
    // 2.如果不想要传递信号,直接创建空的信号[RACSignal empty];
    // 3.RACCommand中信号如果数据传递完,必须调用[subscriber sendCompleted],这时命令才会执行完毕,否则永远处于执行中。
    // 4.RACCommand需要被强引用,否则接收不到RACCommand中的信号,因此RACCommand中的信号是延迟发送的。
    _loginCommand = [[RACCommand alloc]initWithSignalBlock:^RACSignal * _Nonnull(id  _Nullable input) {
        RACSignal *loginSignal = [RACSignal createSignal:^RACDisposable * _Nullable(id  _Nonnull subscriber) {
            ////这里就是模拟网络的一个block
            [Netwrok getData:^(NSString *str) {
                [subscriber sendNext:str];
                [subscriber sendCompleted];
            }] ;
            return  [RACDisposable disposableWithBlock:^{
                NSLog(@"信号销毁");
            }];
        }];
        return loginSignal;
    }];
    ////这里就是订阅command 里面的信号 根据不同信号来跳转 和 处理业务
    [_loginCommand.executionSignals.switchToLatest subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@",x);
        if ([x isEqualToString:@"成功"]) {
            ListViewController *vc = [[ListViewController alloc]init];
            [self.nav pushViewController:vc animated:YES];
        }
    }];
    ////这里就是订阅 command 里面的 信号 使用skip 是过滤第一个信号因为默认会调用一次
    [[_loginCommand.executing skip:1] subscribeNext:^(NSNumber * _Nullable x) {
        NSLog(@"%@",x);
        if ([x isEqual:@(YES)]) {
            NSLog(@"正在登录");
        }else{
            NSLog(@"%@",x);
        }
    }]; 
} 
@end

2.4 最简单的 VC

#import "ViewController.h"
#import "LoginViewModel.h" 
@interface ViewController ()
@property(nonatomic,strong)LoginViewModel *viewModel;
@property (weak, nonatomic) IBOutlet UITextField *nameTextField;
@property (weak, nonatomic) IBOutlet UITextField *pwdTextField;
@property (weak, nonatomic) IBOutlet UIButton *loginBtn;

@end

@implementation ViewController

-(LoginViewModel*)viewModel{
    if (!_viewModel) {
        _viewModel = [[LoginViewModel alloc]init];
    }
    return _viewModel;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    ////这里就是监听 每个控件值的变化
    RAC(self.viewModel.account,userName) = self.nameTextField.rac_textSignal;
    RAC(self.viewModel.account,pwd) = self.pwdTextField.rac_textSignal;
    RAC(self.loginBtn,enabled) = self.viewModel.listeningSignal;
    ///这里是监听按钮的颜色 是有可以点击的时候改变颜色
    ///1. 这种方式是直接 监听按钮的一个enabled 属性,是否可以点击
    [RACObserve(self.loginBtn, enabled) subscribeNext:^(id x) {
        if ([x isEqual:@1]) {
             self.loginBtn.backgroundColor = [UIColor greenColor];
        } else {
             self.loginBtn.backgroundColor = [UIColor redColor];
        }
        NSLog(@"是否可以点击%@",x);
    }];
    ////2. 这种是再viewmodel 里面再创建一个RACSubject 信号 用来发送信号以便改变颜色
//    self.viewModel.btnColorSubject = [RACSubject subject];
//    [self.viewModel.btnColorSubject subscribeNext:^(id  _Nullable x) {
//        if ([x isEqual:@1]) {
//             self.loginBtn.backgroundColor = [UIColor greenColor];
//        } else {
//             self.loginBtn.backgroundColor = [UIColor redColor];
//        }
//        NSLog(@"----%@",x);
//    }];
    /*这里就是 导航栏 放到ViewModel里面。也有人认为放到C里面去跳转。我个人比较倾向在ViewModel里面去跳转,既然ViewModel里面都是出来业务逻辑,那么跳转也应该属于其中一部分。*/
    self.viewModel.nav = self.navigationController;
    ////监听按钮的点击事件,当按钮收到可以点击事件的时候 LoginCommand 就可以执行 execute 执行命名
    [[_loginBtn rac_signalForControlEvents:1<<6] subscribeNext:^(__kindof UIControl * _Nullable x) {
        [self.viewModel.loginCommand execute:nil];
    }];  
} 

@end

2.5 创建一个 NetWork 用来获取数据

#import  
typedef void(^NetWorkBlock)(NSString *str);
typedef void(^NetWorkDataBlock)(id data); 
@interface Netwrok : NSObject
+(void)getData:(NetWorkBlock)block;
+(void)getListData:(NetWorkDataBlock)block;
@end

#import "Netwrok.h" 
@implementation Netwrok 
+(void)getData:(NetWorkBlock)block{
    block(@"成功");
}

+(void)getListData:(NetWorkDataBlock)block{
    NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
    parameters[@"q"] = @"基础";
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    [manager GET:@"https://api.douban.com/v2/book/search" parameters:parameters progress:^(NSProgress * _Nonnull downloadProgress) {
    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        block(responseObject);
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
    }];

}

2.6 创建一个 BookModel 用来传当cell里面的数据模型

@interface BookModel : NSObject
@property(nonatomic,strong)NSString *subtitle;
@property(nonatomic,strong)NSString *author;
@property(nonatomic,strong)NSString *pubdate;
@property(nonatomic,strong)NSString *image;
@property(nonatomic,strong)NSString *binding;
@property(nonatomic,strong)NSString *catalog;
+(BookModel*)bookWithDict:(NSDictionary*)dict;
@end
@implementation BookModel
+(BookModel*)bookWithDict:(NSDictionary *)dict{
    BookModel *book = [[BookModel alloc]init];
    book.subtitle = dict[@"subtitle"];
    book.author = dict[@"author"];
    book.pubdate = dict[@"pubdate"];
    book.image = dict[@"image"];
    book.catalog = dict[@"catalog"];
    return book;
}
@end
#import "BookModel.h"
@interface BookTableViewCell : UITableViewCell
@property(nonatomic,strong)UILabel *titleLabel;
@property(nonatomic,strong)UILabel *dextLabel;
@property(nonatomic,strong)BookModel *book;
+(instancetype)cellWithTableView:(UITableView*)tableView;
@end
@implementation BookTableViewCell

+(instancetype)cellWithTableView:(UITableView*)tableView{
    static NSString *identfier = @"BookTableViewCell";
    //先在缓存池中取
    BookTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identfier];
    //缓存池中没有再创建,并添加标识,cell移出屏幕时放入缓存池以复用
    if (cell == nil) {
        cell = [[BookTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identfier];
    }
    return cell; 
}

-(id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    self=[super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        self.titleLabel = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, 200, 20)];
        [self.contentView addSubview:self.titleLabel];

        self.dextLabel= [[UILabel alloc]initWithFrame:CGRectMake(0, 20, 200, 20)];
        [self.contentView addSubview:self.dextLabel];
    }
    return self;
}

-(void)setBook:(BookModel *)book{
    _book = book;
    self.titleLabel.text = book.subtitle;
    self.dextLabel.text = book.pubdate;
} 
@end

2.7 创建一个 ListViewModel 处理数据返回 将数据展示V上

@interface ListViewModel : NSObject <UITableViewDataSource,UITableViewDelegate>
@property(nonatomic,strong)RACCommand *requeseCommand;
@property(nonatomic,strong)NSArray *models;
@property(nonatomic,strong)RACSubject *subject;
@property(nonatomic,strong)UINavigationController *nav; 
@end
#import "ListViewModel.h"
#import "Network.h"
#import "BookModel.h"
#import "BookTableViewCell.h" 
@implementation ListViewModel 
-(instancetype)init{
    if (self = [super init]) {
        [self initabBind];
    }
    return self;
}  
-(void)initabBind{
    _requeseCommand = [[RACCommand alloc]initWithSignalBlock:^RACSignal * _Nonnull(id  _Nullable input) {
        RACSignal *requestSignal = [RACSignal createSignal:^RACDisposable * _Nullable(id  _Nonnull subscriber) {
            [Network getList:^(id data) {
                [subscriber sendNext:data];
                [subscriber sendCompleted];
            }];
            return [RACDisposable disposableWithBlock:^{
                NSLog(@"信号销毁");
            }];
        }];

        return [requestSignal map:^id _Nullable(id  _Nullable value) {
            NSMutableArray *dictArray = value[@"books"];
            NSArray *modelArray = [[dictArray.rac_sequence map:^id _Nullable(id  _Nullable value) {
                return [BookModel bookWithDict:value];
            }]array];
            return modelArray;
        }];
    }];
}

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    return self.models.count;
}

-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    BookTableViewCell *cell = [BookTableViewCell cellWithTableView:tableView];
    cell.book=self.models[indexPath.row];
    return cell;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    BookModel *book= self.models[indexPath.row];
    UIViewController *vc = [[UIViewController alloc]init];
    vc.title=book.subtitle;
    vc.view.backgroundColor = [UIColor whiteColor];
    [self.nav pushViewController:vc animated:YES];
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    return 50;
} 
@end 

2.8创建C 装载视图 让后执行Command 命名刷新表格数据

#import "ListViewController.h"
#import "ListViewModel.h"
#import "BookModel.h"
@interface ListViewController ()
@property(nonatomic,strong)UITableView *tableView;
@property(nonatomic,strong)ListViewModel *requestViewModel;
@end

@implementation ListViewController
-(ListViewModel*)requestViewModel{
    if (!_requestViewModel) {
        _requestViewModel = [[ListViewModel alloc]init];
    }
    return _requestViewModel;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor=[UIColor whiteColor];
    self.tableView = [[UITableView alloc]initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
    self.tableView.dataSource = self.requestViewModel;
    self.tableView.delegate=self.requestViewModel;
    self.requestViewModel.nav = self.navigationController;
    [self.view addSubview:self.tableView];

    RACSignal *requestSiganl = [self.requestViewModel.requeseCommand execute:nil];
    [requestSiganl subscribeNext:^(id  _Nullable x) {
        self.requestViewModel.models = x;
        [self.tableView reloadData];
    }];
}

在此所有的代码都在这里了,写完你会发现结构确实要比MVC清晰多了。各项职能都很明确。
当然怎么去选择都是根据具体业务去实现,业务简单的完全就可以MVC了。没必要写成这样,复杂一点的,就可以使用MVVM让你的结构变得更加清晰。

你可能感兴趣的:(MVVM+RAC简单使用教程)