MVVM+RAC简单使用教程

来源:互联网 发布:js两个对象数组合并 编辑:程序博客网 时间:2024/06/14 22:59

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 <ReactiveObjC/ReactiveObjC.h>    #import <AFNetworking/AFNetworking.h>

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

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

 #import <Foundation/Foundation.h>@interface AccountModel : NSObject@property(nonatomic,strong)NSString *userName;@property(nonatomic,strong)NSString *pwd;@end
2.3 新建一个 ViewModel
#import <Foundation/Foundation.h>#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<RACSubscriber>  _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 <Foundation/Foundation.h> 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<RACSubscriber>  _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让你的结构变得更加清晰。

原创粉丝点击