ReactiveCocoa学习笔记-应用篇+MVVM架构

首先RAC和MVVM是两个东西,并不是一定要一起用,只不过RAC可以更优雅的实现MVVM的架构

MVVM

经历过一阵子的开发之后,当项目越写越大的时候,不难发现,传统的MVC架构显得和笨重,尤其处理复杂页面的时候,控制器里面可以分化出网络层,服务层,应用层,等等,根据每个项目的状况和每个人的理解程度不同,还可以有很多不同的方案,总之你会发现控制器越来越复杂。

再大量的码代码的阶段的过程中,解耦是永恒的问题,每个人对于继承,封装,协议,block等等的理解方式导致每个人的项目中或多或少都有一些倾向,无论是什么技术,设计出来的架构都是为了能让项目更明朗,更易用,易扩展。所以在项目架构设计的时候,无论是什么技术貌似都有利有弊,所以平衡就显得尤为重要,技术虽好,不要贪杯哦。没有最好的架构,只有最适合的架构。

定义MVVM

  • Model:MVVM定义的model倾向于瘦model,尽量让model只是提供数据——模型之间的转换问题
  • View:view层的定义跟MVC中没有什么差别,更加肯定的是,view的作用只是用来提供UI的罗列,并不存在什么有关的逻辑问题,这样也更容易复用。
  • ViewModel:通俗点理解他可以是控制器中的网络层,也可以是tableView的代理,无论他是什么,他都是view和model的桥梁。
  • Controller:这时候的控制器的作用只是用来调度,与其说是调度,不如说是bind。

实现

需求

1.MVVM+RAC实现列表数据,扩展上拉下拉到基类中,实现复用

2.彻底解耦,View只化UI,可以复用,model只提供数据类型,可以复用

具体实现
先来看看基类,首先是控制器,初始化方法就传入viewmodel,提供三个初始化后调用的接口,也可以根据具体需求修改。

#import "BaseVCProtocol.h"
@class BaseViewModel;
@interface BaseVC : UIViewController
/**  初始化视图 */
- (instancetype)initWithViewModel:(BaseViewModel *)viewModel;

/**  初始化后调用的方法 */
- (void)bindInitialization;
- (void)bindNotification;
- (void)bindViewModel;
@end

#import "BaseVC.h"
#import "BaseViewModel.h"
@interface BaseVC ()

/**  viewModel */
@property(nonatomic,strong) BaseViewModel * viewModel;

@end

@implementation BaseVC

#pragma mark - life cycle
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    BaseVC *viewController = [super allocWithZone:zone];
    
    @weakify(viewController)
    [[viewController
      rac_signalForSelector:@selector(viewDidLoad)]
     subscribeNext:^(id x) {
         @strongify(viewController)
         [viewController bindInitialization];
         [viewController bindNotification];
         [viewController bindViewModel];
     }];
    
    return viewController;
}
- (instancetype)initWithViewModel:(BaseViewModel *)viewModel {
    self = [super init];
    if (self) {
        self.viewModel = viewModel;
    }
    return self;
}
- (void)viewDidLoad
{
    [super viewDidLoad];
}
- (void)bindInitialization {}
- (void)bindNotification {}
- (void)bindViewModel {}
@end

控制器基类还是比较简单,扩展性强,主要是做一些规范性的接口,接着是tableViewVC,包装一层tableView代理,提供网络请求状态接口方便在子类中处理异常的逻辑

tableViewVC中要依赖于里面的viewModel进行一些通用的设置,例如,基础的tableView代理配置,是否在在子类中选择是否有刷新,刷新信号的绑定,cell点击信号的绑定,数据更新时的刷新

#import "BaseVC.h"

@interface BaseTableViewVC : BaseVC
/**  tableView */
@property(nonatomic,strong) UITableView * tableView;
/**  处理成功回调 */
- (void)dataRequestSuccess:(id)params;
/**  处理错误回调 */
- (void)dataRequestError:(NSError *)error;
/**  处理结束回调 */
- (void)dataRequestFinished;
@end
#import "BaseTableViewVC.h"
#import "BaseTableViewModel.h"
@interface BaseTableViewVC ()
/**  viewModel */
@property(nonatomic,strong) BaseTableViewModel * viewModel;
@end

@implementation BaseTableViewVC
#pragma mark - life cycle
- (instancetype)initWithViewModel:(BaseViewModel *)viewModel{
    self = [super initWithViewModel:viewModel];
    if (self) {
            @weakify(self);
            [[self rac_signalForSelector:@selector(bindViewModel)]
             subscribeNext:^(id x) {
                 @strongify(self);
                 //一开始就刷新
                 if (self.viewModel.isAllowLoadData &&self.viewModel.isLoadDataInitially) {
                     [self.tableView.mj_header beginRefreshing];
                 }
             }];
    }
    return self;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    [self.view addSubview:self.tableView];
    if (self.viewModel.isAllowLoadData) {
        self.tableView.mj_header = [self setupHeader];
        self.tableView.mj_footer = [self setupFooter];
    }
    
}
- (void)bindInitialization
{
    [super bindInitialization];
}
- (void)bindNotification
{
    [super bindNotification];
}
- (void)bindViewModel
{
    [super bindViewModel];
    @weakify(self);
    //监听数据源,刷新列表  [RACObserve(self.viewModel,dataSource).distinctUntilChanged.deliverOnMainThread
     subscribeNext:^(id x) {
         @strongify(self);
         [self.tableView reloadData];
     }];
}
#pragma mark - custom
- (void)dataRequestSuccess:(id)params {}
- (void)dataRequestError:(NSError *)error {}
- (void)dataRequestFinished {}
- (MJRefreshNormalHeader *)setupHeader
{
    @weakify(self);
    MJRefreshNormalHeader * setupHeader = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
        @strongify(self);
        self.viewModel.pageIndex = 0;
        [self requestRemote];
    }];
    return setupHeader;
}
- (MJRefreshBackNormalFooter *)setupFooter
{
    @weakify(self);
    MJRefreshBackNormalFooter * setupFooter = [MJRefreshBackNormalFooter footerWithRefreshingBlock:^{
        @strongify(self);
        [self requestRemote];
    }];
    return setupFooter;
}
- (void)requestRemote
{
    @weakify(self);
    [[self.viewModel.requestRemoteDataCommand execute:@(self.viewModel.pageIndex + 1)] subscribeNext:^(id  _Nullable x) {
        @strongify(self);
        if ([self respondsToSelector:@selector(dataRequestSuccess:)]) {
            [self dataRequestSuccess:x];
        }
        [self.tableView.mj_header endRefreshing];
        [self.tableView.mj_footer endRefreshing];
    } error:^(NSError * _Nullable error) {
        @strongify(self);
        if ([self respondsToSelector:@selector(dataRequestError:)]) {
            [self dataRequestError:error];
        }
        [self.tableView.mj_header endRefreshing];
        [self.tableView.mj_footer endRefreshing];
    } completed:^{
        @strongify(self);
        if ([self respondsToSelector:@selector(dataRequestFinished)]) {
            [self dataRequestFinished];
        }
        [self.tableView.mj_header endRefreshing];
        [self.tableView.mj_footer endRefreshing];
    }];
}
#pragma mark - UITableViewDelegate
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return self.viewModel.dataSource.count;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return 44.0f;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *ID = @"cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID];
    }
    cell.textLabel.text = [NSString stringWithFormat:@"%ld-----%ld",indexPath.section,indexPath.row];
    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    [self.viewModel.cellDidSelect execute:indexPath];
}
#pragma mark - lazy
- (UITableView *)tableView
{
    if (!_tableView) {
        _tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
        _tableView.backgroundColor = [UIColor whiteColor];
        _tableView.delegate = self;
        _tableView.dataSource = self;
        _tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
        [self.view addSubview:_tableView];
    }
    return _tableView;
}
- (BaseViewModel *)viewModel
{
    return [[BaseTableViewModel alloc] init];
}
@end

基本的viewModel还是提供最基本的接口供子类重写,并提供一个数据的信号,可以在子类封装出使用的网络请求

#import "BaseViewModelProtocol.h"

@interface BaseViewModel : NSObject

/**  初始化后调用的方法 */
- (void)viewModelDidLoad;
- (void)viewModelLoadNotifications;
/**  获取数据方法 */
- (RACSignal *)requestRemoteDataSignal;

@end
#import "BaseViewModel.h"

@interface BaseViewModel ()

@end

@implementation BaseViewModel
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    BaseViewModel *viewModel = [super allocWithZone:zone];
    
    @weakify(viewModel)
    [[viewModel
      rac_signalForSelector:@selector(init)]
     subscribeNext:^(id x) {
         @strongify(viewModel)
         [viewModel viewModelDidLoad];
         [viewModel viewModelLoadNotifications];
     }];
    
    return viewModel;
}

- (void)viewModelDidLoad {}
- (void)viewModelLoadNotifications {}
#pragma mark - custom
- (RACSignal *)requestRemoteDataSignal
{
    return [RACSignal empty];
}
#pragma mark - lazy
@end

然后为tableView提供一个专门的viewModel,来配置刷新状态,操作刷新的逻辑处理,网络请求,点击事件和刷新的信号发送

#import "BaseViewModel.h"

//默认每次请求显示10条数据
static const NSInteger pageSize = 10;

@interface BaseTableViewModel : BaseViewModel
/**  是否一开始就需要刷新 */
@property (nonatomic,assign) BOOL isLoadDataInitially;
/**  是否允许刷新 */
@property (nonatomic,assign) BOOL isAllowLoadData;
/**  是否允许加载更多 */
@property (nonatomic,assign) BOOL isAllowLoadAdditionalData;
/**  点击cell指令 */
@property (nonatomic,strong) RACCommand * cellDidSelect;
/**  获取更多数据的指令 */
@property (nonatomic,strong) RACCommand *requestRemoteDataCommand;
/**  数据源 */
@property(nonatomic,strong) NSArray * dataSource;
/**  pageIndex */
@property(nonatomic,assign) NSInteger pageIndex;
/**  获取数据方法(重写) */
- (RACSignal *)requestRemoteDataSignalWithPage:(NSUInteger)page;
/**  默认处理数据回调的方法(上拉下拉处理数据) */
- (void)dataSourceHandler:(NSArray *)arr;
@end
#import "BaseTableViewModel.h"


@implementation BaseTableViewModel
#pragma mark - life cycle
- (void)viewModelDidLoad {
    [super viewModelDidLoad];
    
    self.pageIndex = 0;
    self.dataSource = [NSMutableArray array];
    
    @weakify(self);
    self.requestRemoteDataCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(NSNumber *page) {
        @strongify(self);
        return [self requestRemoteDataSignalWithPage:page.unsignedIntegerValue];
    }];
    self.cellDidSelect = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(NSIndexPath *  _Nullable indexPath) {
        return [RACSignal createSignal:^RACDisposable * _Nullable(id  _Nonnull subscriber) {
            [subscriber sendNext:indexPath];
            [subscriber sendCompleted];
            return nil;
        }];
    }];
}
- (void)viewModelLoadNotifications
{
    [super viewModelLoadNotifications];
}
#pragma mark - network
- (RACSignal *)requestRemoteDataSignalWithPage:(NSUInteger)page
{
    return [RACSignal empty];
}
#pragma mark - custom
- (void)dataSourceHandler:(NSArray *)arr {
    if (arr && arr.count > 0) {
        if (self.pageIndex == 0) {
            self.dataSource = arr;
        } else {
            self.dataSource = [self.dataSource arrayByAddingObjectsFromArray:arr];
        }
        self.pageIndex ++;
    } else {
        if (self.pageIndex == 0) {
            self.dataSource = @[];
        }
    }
}
#pragma mark - lazy

基于这些基础,扩展起来就很容易,代码也很少。view只需要去布局就好了,model只写数据结构,中间通过一个协议进行赋值,高度如果手动算的话就实现协议

@protocol BaseTableViewCellProtocol 
@optional
/**  绑定ViewModel以及赋值 */
- (void)bindWithViewModel:(BaseViewModel *)viewModel fetchDataSource:(NSObject *)dataSource forIndexPath:(NSIndexPath *)indexPath;
/**  计算高度 */
+ (CGFloat)cellHeightWithModel:(NSObject *)model;
@end

子类的viewModel只需要重写网络请求信号,并解析

#import "SectionMVVM_ViewModel.h"
#import "SectionMVVM_Model.h"

@implementation SectionMVVM_ViewModel
#pragma mark - life cycle
- (void)viewModelDidLoad
{
    [super viewModelDidLoad];
    self.isAllowLoadData = YES;
}

- (void)viewModelLoadNotifications
{
    [super viewModelLoadNotifications];
}
#pragma mark - network
- (NSArray *)networkHandle
{
    NSDictionary * dic = @{@"image":@"http://pic6.huitu.com/res/20130116/84481_20130116142820494200_1.jpg",
                           @"title":@"百度图片",
                           @"content":@"百度图片使用世界前沿的人工智能技术,为用户甄选海量的高清美图,用更流畅、更快捷、更精准的搜索体验,带你去发现多彩的世界。"};
    NSMutableArray *array = [NSMutableArray array];
    for (NSInteger i = 0; i < 20; i++) {
        [array addObject:dic];
    }
    return array;
}
/**  获取数据方法(重写) */
- (RACSignal *)requestRemoteDataSignalWithPage:(NSUInteger)page
{
    @weakify(self)
    return [[[[[ZZRequestManager sectionMVVMRequestWithPage:page] map:^id _Nullable(NSArray *  _Nullable listArray) {
        return [NSArray yy_modelArrayWithClass:[SectionMVVM_Model class] json:listArray];
    }] doNext:^(id  _Nullable x) {
        @strongify(self)
        [self dataSourceHandler:x];
    }] doError:^(NSError * _Nonnull error) {
        @strongify(self)
        NSArray * array = [NSArray yy_modelArrayWithClass:[SectionMVVM_Model class] json:[self networkHandle]];
        
        [self dataSourceHandler:array];
    }] doCompleted:^{

    }];
}
#pragma mark - custom

#pragma mark - lazy
@end

子类控制器如果想自定义什么就重写什么方法,初始化的时候绑定上viewModel的网络请求和点击事件

#import "SectionMVVM_VC.h"
#import "SectionMVVM_Cell.h"
#import "SectionMVVM_ViewModel.h"

@interface SectionMVVM_VC ()
/**  viewModel */
@property(nonatomic,strong) SectionMVVM_ViewModel * viewModel;
@end

@implementation SectionMVVM_VC
#pragma mark - life cycle
- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.tableView registerClass:[SectionMVVM_Cell class] forCellReuseIdentifier:NSStringFromClass([SectionMVVM_Cell class])];
}

- (void)bindViewModel
{
    [super bindViewModel];
    [self.viewModel.requestRemoteDataCommand execute:@(1)];
    
    [self.viewModel.cellDidSelect.executionSignals.switchToLatest subscribeNext:^(NSIndexPath *  _Nullable indexPath) {
        NSLog(@"%ld--%ld",indexPath.section,indexPath.row);
    }];
}


#pragma mark - customMethod


#pragma mark - reset
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    SectionMVVM_Cell * cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([SectionMVVM_Cell class])];
    [cell bindWithViewModel:self.viewModel fetchDataSource:self.viewModel.dataSource[indexPath.row] forIndexPath:indexPath];
    return cell;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return [tableView fd_heightForCellWithIdentifier:NSStringFromClass([SectionMVVM_Cell class]) cacheByIndexPath:indexPath configuration:^(SectionMVVM_Cell * cell) {
            [cell bindWithViewModel:self.viewModel fetchDataSource:self.viewModel.dataSource[indexPath.row] forIndexPath:indexPath];
    }];
}
#pragma mark - lazy or setter or getter

总结

由于手中现在的项目做的越来越复杂,控制器的代码逐渐增加,从最开始200行到现在400行,实在是无法忍受了(其实看别人的项目,控制器400行我肯定看不下去),再寻找合适框架的时候,顺便学了一下RAC,写个demo巩固一下所学的知识,最近时间比较紧张,可能还有很多地方可以优化,大神勿喷。

demo地址:https://github.com/754340156/RAC-MVVM

你可能感兴趣的:(ReactiveCocoa学习笔记-应用篇+MVVM架构)