MVVM+RAC

iOS开发之RAC+MVVM实战

2019-07-12  本文已影响0人  代码歌

简介

实战

本文介绍两个开发中常用的场景,第一个是UITableView列表界面通过网络请求数据展示数据,第二个是登录功能。功能比较基础,但都是精髓。分享一下笔者对MVVM的一些见解,在此抛砖引玉,希望能对广大开发者提供一点思路。

一、UITableView列表
订单列表.png

效果如上图,实现此功能用到的类:

1、OrderController

#import "OrderController.h"
#import "RequestViewModel.h"
#import "OrderCell.h"

@interface OrderController ()<UITableViewDataSource, UITableViewDelegate>
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (strong, nonatomic) RequestViewModel *reqVM;
@end

@implementation OrderController

- (void)viewDidLoad {
   [super viewDidLoad];
   [self setUI];
   [self ViewModelEvent];
}
#pragma mark - 界面设置
- (void)setUI {
   self.tableView.dataSource = self;
   self.tableView.delegate = self;
   self.tableView.rowHeight = 100;
   [self.tableView registerNib:[UINib nibWithNibName:@"OrderCell" bundle:nil] forCellReuseIdentifier:@"OrderCell"];
}
#pragma mark - ViewModel事件
- (void)ViewModelEvent {
   [self.reqVM.reqCommand execute:nil];
   @weakify(self);
   [self.reqVM.refreshUISubject subscribeNext:^(id x) {
      @strongify(self);
      [self.tableView reloadData];
   }];
}
#pragma mark - UITableView配置
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
   return self.reqVM.dataArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
   OrderCell *cell = [tableView dequeueReusableCellWithIdentifier:@"OrderCell"];
   cell.model = self.reqVM.dataArray[indexPath.row];
   return cell;
}
#pragma mark - 懒加载
- (RequestViewModel *)reqVM {
   if (!_reqVM) {
      _reqVM = [[RequestViewModel alloc] init];
   }
   return _reqVM;
}

@end

OrderController主要讲的是ViewModelEvent中的方法,其他也没什么可说的

2、RequestViewModel:主要向控制器提供数据,通知tableView刷新界面

RequestViewModel.h

#import <Foundation/Foundation.h>
#import <ReactiveObjC/ReactiveObjC.h>

@interface RequestViewModel : NSObject

@property (nonatomic, strong) RACSubject *refreshUISubject;
@property (strong, nonatomic) RACCommand *reqCommand;
@property (nonatomic, strong) NSArray *dataArray;

@end

RequestViewModel.m

#import "RequestViewModel.h"
#import "OrderModel.h"
#import "AFNetworking.h"
#import "MBProgressHUD+Add.h"
#import "MJExtension.h"

@interface RequestViewModel ()

@end

@implementation RequestViewModel

- (instancetype)init {
    if (self = [super init]) {
        [self or_initialize];
    }
    return self;
}
- (void)or_initialize {
    [self.reqCommand.executionSignals.switchToLatest subscribeNext:^(NSDictionary *dic) {
        NSArray *items = dic[@"items"];
        self.dataArray = [OrderModel mj_objectArrayWithKeyValuesArray:items];
        [self.refreshUISubject sendNext:nil];
    }];
    [[self.reqCommand.executing skip:1] subscribeNext:^(id x) {
        if ([x isEqualToNumber:@(YES)]) {
            [MBProgressHUD showCircleHud:nil];
        }else {
            [MBProgressHUD closeHud:nil];
        }
    }];
}
#pragma mark - 懒加载
- (RACCommand *)reqCommand {
    if (!_reqCommand) {
        _reqCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id  _Nullable input) {
            //因为要把请求的数据传出去,所以要把网络请求包装在信号里
            RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
                NSDictionary *dic = @{@"action":@"getProduct",@"page":@"0"};
                NSString *url = @"http://10.49.3.125:8080/";
                
                AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
                manager.responseSerializer = [AFHTTPResponseSerializer serializer];
                manager.requestSerializer = [AFHTTPRequestSerializer serializer];
                [manager GET:url parameters:dic progress:^(NSProgress * _Nonnull downloadProgress) {
                    
                } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                    NSError * error;
                    NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingMutableContainers error:&error];
                    [subscriber sendNext:dic];
                    [subscriber sendCompleted];
                } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                    [MBProgressHUD showMessage:@"网络连接失败" toView:nil];
                    [subscriber sendCompleted];
                }];
                return nil;
            }];
            //返回网络请求信号
            return signal;
        }];
    }
    return _reqCommand;
}
- (RACSubject *)refreshUISubject {
    if (!_refreshUISubject) {
        _refreshUISubject = [RACSubject subject];
    }
    return _refreshUISubject;
}
- (NSArray *)dataArray {
    if (!_dataArray) {
        _dataArray = [[NSArray alloc] init];
    }
    return _dataArray;
}

@end

3、OrderCell和OrderModel

跟之前MVC做法完全一致,其实没什么好说的

OrderCell.h

#import <UIKit/UIKit.h>
#import "OrderModel.h"

@interface OrderCell : UITableViewCell

@property (nonatomic, strong) OrderModel *model;

@end

OrderCell.m

#import "OrderCell.h"
#import "SDWebImage.h"

@interface OrderCell ()
@property (weak, nonatomic) IBOutlet UIImageView *imgV;
@property (weak, nonatomic) IBOutlet UILabel *nameLab;
@property (weak, nonatomic) IBOutlet UILabel *typeLab;
@property (weak, nonatomic) IBOutlet UILabel *descLab;

@end

@implementation OrderCell

- (void)awakeFromNib {
    [super awakeFromNib];
    // Initialization code
}
- (void)setModel:(OrderModel *)model {
    [_imgV sd_setImageWithURL:[NSURL URLWithString:model.imageUrl]];
    _nameLab.text = model.name;
    _typeLab.text = model.type;
    _descLab.text = model.desc;
}

@end

OrderModel.h

#import <Foundation/Foundation.h>

@interface OrderModel : NSObject

@property (nonatomic, copy) NSString *desc;
@property (nonatomic, copy) NSString *imageUrl;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *type;

@end
二、登录功能
登录.png

效果如上图,实现此功能用到的类:

1、LoginController

#import "LoginController.h"
#import "LoginViewModel.h"

@interface LoginController ()
@property (weak, nonatomic) IBOutlet UITextField *numTextField;
@property (weak, nonatomic) IBOutlet UITextField *pwdTextField;
@property (weak, nonatomic) IBOutlet UIButton *loginBtn;
@property (strong, nonatomic) LoginViewModel *loginVM;
@end

@implementation LoginController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self bindViewModel];
    [self loginEvent];
}
#pragma mark - ViewModel处理
- (void)bindViewModel {
    //给ViewModel账号密码绑定信号
    RAC(self.loginVM,num) = _numTextField.rac_textSignal;
    RAC(self.loginVM,pwd) = _pwdTextField.rac_textSignal;
}
- (void)loginEvent {
    //把_loginBtn的enabled属性与信号绑定
    RAC(_loginBtn,enabled) = self.loginVM.loginEnabledSignal;
    //登录按钮点击事件
    [[_loginBtn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
        [self.loginVM.loginCommand execute:nil ];
    }];
}
#pragma mark - 懒加载
- (LoginViewModel *)loginVM {
    if (!_loginVM) {
        _loginVM = [[LoginViewModel alloc] init];
    }
    return _loginVM;
}

@end

2、LoginViewModel

#import <Foundation/Foundation.h>
#import <ReactiveObjC/ReactiveObjC.h>

@interface LoginViewModel : NSObject

@property (copy, nonatomic) NSString *num;
@property (copy, nonatomic) NSString *pwd;

//按钮是否被允许点击
@property (strong, nonatomic, readonly) RACSignal *loginEnabledSignal;
//登录按钮命令
@property (strong, nonatomic, readonly) RACCommand *loginCommand;

@end
#import "LoginViewModel.h"
#import "MBProgressHUD+Add.h"

@implementation LoginViewModel

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

- (void)setRACSignal {
    _loginEnabledSignal = [RACSignal combineLatest: @[RACObserve(self, num),RACObserve(self, pwd)] reduce:^id (NSString *num, NSString *pwd){
        //账号输入位数大于0,密码大于等于6时登录按钮可点击
        BOOL isEnabled = (num.length > 0 && pwd.length >= 6) ? YES : NO;
        return @(isEnabled);
    }];
    //处理登录点击:创建登录命令。(只要处理事件,就要用到RACCommand)
    _loginCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id  _Nullable input) {
        
        return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
            //模拟请求登录数据
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                [subscriber sendNext:@"模拟登录请求"];
                [subscriber sendCompleted]; //一定要写
            });
            return nil;
        }];
    }];
    //订阅命令中的信号
    [_loginCommand.executionSignals.switchToLatest subscribeNext:^(id  _Nullable x) {
        //这里写保存服务器返回的信息
    }];
    //监听命令执行过程
    //skip:1跳过第一次信号,因为刚开始没有执行的时候x也为NO
    [[_loginCommand.executing skip:1] subscribeNext:^(NSNumber * _Nullable x) {
        if ([x boolValue] == YES) {
            [MBProgressHUD showCircleHud:nil];
        }else {
            //执行完成
            [MBProgressHUD closeHud:nil];
            [MBProgressHUD showMessage:@"登陆成功" toView:nil];
        }
    }];
}

@end
上一篇 下一篇

猜你喜欢

热点阅读