iOS MVVM+RAC 再不学你就OUT了


严肃的目录
一、前言
1.写之前的废话
二、MVVM和RAC理解
1.MVVM浅谈
2.RAC浅谈
3.两者的结合运用
三、架构部分
1.演示架构详解
2.基类说明
3.对上述吧啦吧啦几句
四、实战部分
1.ViewController的处理
2.View的处理
3.Model的处理
4.ViewModel的处理
五、后记
1.程序员话痨模式开启
六、基情感谢
1.程序员之间的满满基情

一、写之前吧啦吧啦几句

经过第一篇文章的多愁善感,接下来是表演真正的绝技的时候了,回归我的老本行,宇宙无敌iOS软件开发工程师!(装逼完毕,此处可以有点掌声)最近的IT行情不太好了,很多小狮子们都找不到工作了,面试的时候,也会经常被问到MVC等设计模式和架构,今天我就谈谈自己的理解,当然了,我也是刚从小白变成小白发点黑,刚接手的公司的项目用到的是MVVM+RAC的框架,现在终于有那么点滴的了解了,希望能帮助你那么一点,就是我的初衷了。设计模式这东西,说简单也难,说难也不难,嘎嘎嘎,总之万变不离其宗,用到MVVM我们的目标就是,让VC这个胖子减减肥,代码看起来颜值高点,维护的时候方便点,数据处理更清晰点,耦合度低一点,可复用性高点,独立开发更简单点.....也就是那么一点优点啦。废话不多说,开始ing。

二、MVVM和RAC理解

1.MVVM浅谈

MVC是构建iOS App的标准模式,是苹果推荐的一个用来组织代码的权威范式,市面上大部分App都是这样构建的,具体组建模式不细说,iOS入门者都比较了解(虽然不一定能完全去遵守),但其几个不能避免的问题却是很严重困扰开发者比如厚重的ViewController、遗失的网络逻辑(没有属于它的位置)、较差的可测试性等因此也就会有维护性较强、耦合性很低的一种新架构MVVM (MVC 引申出得新的架构)的流行。

MVVM虽然来自微软,但是不应该反对它,它正式规范了正式规范了视图和控制器紧耦合的性质,如下图:

MVVM1.png
MVVM2.png

写到这里,我看见下面有同学🙋说,ViewModel是什么鬼?
View绑定到ViewModel,然后执行一些命令在向它请求一个动作。而反过来,ViewModel跟Model通讯,告诉它更新来响应UI。这样便使得应用构建UI非常的容易。相比较于MVC新引入的视图模型。是视图显示逻辑、验证逻辑、网络请求等代码存放的地方,唯一要注意的是,任何视图本身的引用都不应该放在VM中,换句话说就是VM中不要引入UIKit.h。

这样,首先解决了VC臃肿的问题,将逻辑代码、网络请求等都写入了VM中,然后又由于VM中包含了所有的展示逻辑而且不会引用V,所以它是可以通过编程充分测试的。

2.RAC浅谈

ReactiveCocoa是响应式编程(FRP)在iOS中的一个实现框架,它的开源地址为:https://github.com/ReactiveCocoa/ReactiveCocoa# ;在网上看了几篇文章,感觉理论讲了很多,但是代码还是看不太懂,于是自己把它github文档上的一些使用的经典示例实现了一下,项目中有需要时可以直接搬过去用,用的熟练了再读源码也比较容易理解。

RAC虽然最大的优点是提供了一个单一的、统一的方法去处理异步的行为,包括delegate方法,blocks回调,target-action机制,notifications和KVO.但是不要简单的只是单纯的认为他仅仅就是减少代码复杂度,更好的配合MVVM而已,小伙子,这样你就小看它了。
它最大的与众不同是提供了一种新的写代码的思维,**由于RAC将Cocoa中KVO、UIKit event、delegate、selector等都增加了RAC支持,所以都不用去做很多跨函数的事。

这里我就不细说了,里面的东西还是很多滴,需要慢慢沉淀,这里我推荐一篇写的比较好的教程,大家互相学习。http://www.jianshu.com/p/87ef6720a096

3.两者结合的应用

在此次介绍中,会使用MVVM+RAC结合的方式,搞定一个添加上拉加载及下拉刷新的列表,所以更多的诠释MVVM思想,而不是RAC的逻辑链式操作(这一点用登录界面来写更能体现YoY ),RAC在此扮演的更大一部分的角色是更好的解耦,减少代码复杂度,使代码层次分明、逻辑清晰更便于维护升级。

二、架构部分

1.演示架构详解

废话不说半句多,原谅我这么不爱说话,啊啊啊啊,看图!


架构图.png

Frameworks
存放系统库的文件夹,新建工程的时候自己创建一个这个名字的文件夹,添加的系统库就会乖乖进来啦😄

AppDelegate
谁不知道这是干嘛的,你过来,我保证请你吃肉

Cl0ass
工程的主体,日常代码就放到这里了

General : 通用类(文件夹项目移植过程中都不需要更改的就能直接使用的)Base : 基类 (整个框架的基类)
Categories : 公共扩展类 (就是一些常用的类别,比如分享啊什么的)
Core : 公共核心类(一般存放个人信息、接口API等)
Models : 公共Model (公用的一些数据模型)
Views : 公共View (封装的一些常用的View)

Helpers : 工程的相关辅助类(比如类似数据请求、表单上传、网络监测等工具类)
** Macro :** 宏定义类
** Sections :** 各模块的文件夹(一般而言,我们以人为单位,毕竟项目很多时候是多人开发,每个人创建一个Sections互不影响,里面就存放自己的所有代码和页面啦)
Vendors : 第三方的类库/SDK,如UMeng、WeiboSDK、WeixinSDK等等。

Resource
这里放置的是工程所需的一些资源,如下
Fonts 字体
Images 图片
Sounds 声音
Videos 视频

Pods
当你开发iOS应用时,会经常使用到很多第三方开源类库,比如JSONKit,AFNetWorking等等。可能某个类库又用到其他类库,所以要使用它,必须得另外下载其他类库,而其他类库又用到其他类库,“子子孙孙无穷尽也”,反正在早期我是体会过这种痛苦,好心酸,手动一个个去下载所需类库是十分麻烦的。

还有另外一种常见情况是,你项目中用到的类库有更新,你必须得重新下载新版本,重新加入到项目中,十分麻烦。
CocoaPods就是帮你解决上面的问题的,话说这玩意应该是iOS最常用最有名的类库管理工具了,作为iOS程序员的我们,掌握CocoaPods的使用是必不可少的基本技能了,至于这玩意该咋用?

6666

嘎嘎嘎,如果你看的足够细心,给你们点福利,这个网站可以学习哈,哎,我心软 http://code4app.com/article/cocoapods-install-usage

2.基类详解

这里着重讲解一下VC,View和ViewModel的基类。

基类png

1.YDViewController

VC介绍.png

yd_addSubviews : 添加View到ViewController

yd_bindViewModel : 用来绑定V(VC)与VM

yd_layoutNavigation : 设置导航栏、分栏

yd_getNewData : 初次获取数据的时候调用(不是特别必要)

2.YDView

yd_setupViews : 添加子View到主View
yd_bindViewModel : 绑定V与VM
yd_addReturnKeyBoard : 设置点击空白键盘回收

3.YDViewModel

yc_initialize : 进行一些逻辑绑定,网络数据请求处理。
LSRefreshDataStatus 数据处理后需要进行的操作标识LSHeaderRefresh_HasMoreData 下拉还有更多数据
LSHeaderRefresh_HasNoMoreData 下拉没有更多数据
LSFooterRefresh_HasMoreData 上拉还有更多数据
LSFooterRefresh_HasNoMoreData 上拉没有更多数据
LSRefreshError 刷新出错
LSRefreshUI 仅仅刷新UI布局

3.对上述吧啦吧啦几句

基类的作用是统一管理,统一风格,便于编码,有更多的额外的附加功能的话,建议使用Protocol 或 Category,这样移植性强,便于管理与扩展,不至于牵一发而动全身。
本篇基类核心是用VM来配置V(VC),并提供一些必须的Protocol方法来处理界面显示、逻辑,将代码风格规范化,各个部分的功能明朗化,这样,当你需要写什么,需要找什么,需要更改什么的时候都会很明确这些代码的位置,逻辑更清晰,而不会浪费更多的时间在思考应该写在哪,该去哪找,要改的地方在哪这种不该费时间的问题上。

四、实战部分

接下来,实现一个最基本的列表,由于本人比较懒,用我小伙伴的demo讲解下。话不多说,半句多,嘎嘎嘎,啦啦啊。

列表图片.png

然后处理完后的目录如下

目录结构.png

简单介绍一下:
ViewController****LSCircleListViewController : 界面主控制器,负责跳转、Navgation、TabBar等

View****LSCircleListView : 界面主View,负责主要界面的显示
LSCircleListHeaderView : 头部Header,封装的内部含有一个CollectionView
LSCircleListCollectionCell : 头部Header中的CollectionView自定义的Cell
LSCircleListSectionHeaderView : SectionView,此界面不需复用,所以单纯一个View即可,若需要复用需要TableViewHeaderFooterView
LSCircleListTableCell : 主TableView的Cell

ViewModel****LSCircleListViewModel : 界面主ViewModel
LSCircleListHeaderViewModel : 头部Header对应的ViewModel
LSCircleListCollectionCellViewModel : 头部CollectionCell及TableViewCell的ViewModel(因为二者的数据结构是一致的)
LSCircleListSectionHeaderViewModel : Section的ViewModel

Model****LSCircleListModel : 圈子的数据模型(header和tableViewCell数据结构是一致的)

一个小小的界面这么多类...是不是难以接受了,淡定些,骚年!你要想想把这些个东西都放在VC内是个什么赶脚?也得好几千行呢!(有点夸张!不过也够头疼的),这么多类,这里着重讲一下主VC、主V、主VM、主M就ok,能详细讲明白MVVM之间是如何工作的就一通百通了。

1、LSCircleListViewController的处理

//
//  LSCircleListViewController.m
//  ZhongShui
//
//  Created by 王隆帅 on 16/3/10.
//  Copyright © 2016年 王隆帅. All rights reserved.
//

#import "LSCircleListViewController.h"
#import "LSCircleListView.h"
#import "LSCircleListViewModel.h"
#import "LSCircleMainPageViewController.h"
#import "LSCircleMainPageViewModel.h"
#import "LSCircleListCollectionCellViewModel.h"
#import "LSNewCircleListViewController.h"

@interface LSCircleListViewController ()

@property (nonatomic, strong) LSCircleListView *mainView;

@property (nonatomic, strong) LSCircleListViewModel *viewModel;

@end

@implementation LSCircleListViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
}

#pragma mark - system
- (void)updateViewConstraints {

    WS(weakSelf)

    [self.mainView mas_makeConstraints:^(MASConstraintMaker *make) {

        make.edges.equalTo(weakSelf.view);
    }];

    [super updateViewConstraints];
}

#pragma mark - private
- (void)yc_addSubviews {

    [self.view addSubview:self.mainView];
}

- (void)yc_bindViewModel {

    @weakify(self);
    [[self.viewModel.cellClickSubject takeUntil:self.rac_willDeallocSignal] subscribeNext:^(LSCircleListCollectionCellViewModel *viewModel) {

        @strongify(self);

        LSCircleMainPageViewModel *mainViewModel = [[LSCircleMainPageViewModel alloc] init];
        mainViewModel.headerViewModel.circleId = viewModel.idStr;
        mainViewModel.headerViewModel.headerImageStr = viewModel.headerImageStr;
        mainViewModel.headerViewModel.title = viewModel.name;
        mainViewModel.headerViewModel.numStr = viewModel.peopleNum;

        LSCircleMainPageViewController *circleMainVC = [[LSCircleMainPageViewController alloc] initWithViewModel:mainViewModel];
        [self.rdv_tabBarController setTabBarHidden:YES animated:YES];
        [self.navigationController pushViewController:circleMainVC animated:YES];
    }];

    [self.viewModel.listHeaderViewModel.addNewSubject subscribeNext:^(id x) {

        @strongify(self);
        LSNewCircleListViewController *newCircleListVC = [[LSNewCircleListViewController alloc] init];
        [self.rdv_tabBarController setTabBarHidden:YES animated:YES];
        [self.navigationController pushViewController:newCircleListVC animated:YES];
    }];
}

- (void)yc_layoutNavigation {

    self.title = @"圈子列表";
    [self.rdv_tabBarController setTabBarHidden:NO animated:YES];
}

#pragma mark - layzLoad
- (LSCircleListView *)mainView {

    if (!_mainView) {

        _mainView = [[LSCircleListView alloc] initWithViewModel:self.viewModel];
    }

    return _mainView;
}

- (LSCircleListViewModel *)viewModel {

    if (!_viewModel) {

        _viewModel = [[LSCircleListViewModel alloc] init];
    }

    return _viewModel;
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/

@end

对于VC,分为三个模块,下面分别来说一下:

i 第一个模块:系统函数


此函数是从iOS6.0开始在ViewController中新增一个更新约束布局的方法,这个方法默认的实现是调用对应View的updateConstraints
。ViewController的View在更新视图布局时,会先调用ViewController的updateViewConstraints 方法。我们可以通过重写这个方法去更新当前View的内部布局,而不用再继承这个View去重写-updateConstraints方法。我们在重写这个方法时,务必要调用 super 或者 调用当前View的 -updateConstraints 方法。

ⅱ 第二个模块 : 私有函数

前面基类内也提到了这三个函数的具体作用,即
yd_addSubviews : 添加View到ViewController

yd_bindViewModel : 这里绑定了两个跳转事件。

yd_layoutNavigation : 设置了标题为“圈子列表”、及TabBar不隐藏

ⅲ 第三个模块 : 懒加载

2、View的处理

//
//  LSCircleListView.m
//  ZhongShui
//
//  Created by 王隆帅 on 16/3/10.
//  Copyright © 2016年 王隆帅. All rights reserved.
//

#import "LSCircleListView.h"
#import "LSCircleListViewModel.h"
#import "LSCircleListHeaderView.h"
#import "LSCircleListSectionHeaderView.h"
#import "LSCircleListTableCell.h"

@interface LSCircleListView () <UITableViewDataSource, UITableViewDelegate>

@property (strong, nonatomic) LSCircleListViewModel *viewModel;

@property (strong, nonatomic) UITableView *mainTableView;

@property (strong, nonatomic) LSCircleListHeaderView *listHeaderView;

@property (strong, nonatomic) LSCircleListSectionHeaderView *sectionHeaderView;

@end
@implementation LSCircleListView


/*
 // Only override drawRect: if you perform custom drawing.
 // An empty implementation adversely affects performance during animation.
 - (void)drawRect:(CGRect)rect {
 // Drawing code
 }
 */

#pragma mark - system

- (instancetype)initWithViewModel:(id<YCViewModelProtocol>)viewModel {

    self.viewModel = (LSCircleListViewModel *)viewModel;
    return [super initWithViewModel:viewModel];
}

- (void)updateConstraints {

    WS(weakSelf)
    [self.mainTableView mas_makeConstraints:^(MASConstraintMaker *make) {

        make.edges.equalTo(weakSelf);
    }];
    [super updateConstraints];
}

#pragma mark - private
- (void)yc_setupViews {

    [self addSubview:self.mainTableView];
    [self setNeedsUpdateConstraints];
    [self updateConstraintsIfNeeded];
}

- (void)yc_bindViewModel {

    [self.viewModel.refreshDataCommand execute:nil];

    @weakify(self);

    [self.viewModel.refreshUI subscribeNext:^(id x) {

        @strongify(self);
        [self.mainTableView reloadData];
    }];

    [self.viewModel.refreshEndSubject subscribeNext:^(id x) {
        @strongify(self);

        [self.mainTableView reloadData];

        switch ([x integerValue]) {
            case LSHeaderRefresh_HasMoreData: {

                [self.mainTableView.mj_header endRefreshing];

                if (self.mainTableView.mj_footer == nil) {

                    self.mainTableView.mj_footer = [MJRefreshBackNormalFooter footerWithRefreshingBlock:^{
                        @strongify(self);
                        [self.viewModel.nextPageCommand execute:nil];
                    }];
                }
            }
                break;
            case LSHeaderRefresh_HasNoMoreData: {

                [self.mainTableView.mj_header endRefreshing];
                self.mainTableView.mj_footer = nil;
            }
                break;
            case LSFooterRefresh_HasMoreData: {

                [self.mainTableView.mj_header endRefreshing];
                [self.mainTableView.mj_footer resetNoMoreData];
                [self.mainTableView.mj_footer endRefreshing];
            }
                break;
            case LSFooterRefresh_HasNoMoreData: {
                [self.mainTableView.mj_header endRefreshing];
                [self.mainTableView.mj_footer endRefreshingWithNoMoreData];
            }
                break;
            case LSRefreshError: {

                [self.mainTableView.mj_footer endRefreshing];
                [self.mainTableView.mj_header endRefreshing];
            }
                break;

            default:
                break;
        }
    }];
}

#pragma mark - lazyLoad
- (LSCircleListViewModel *)viewModel {

    if (!_viewModel) {

        _viewModel = [[LSCircleListViewModel alloc] init];
    }

    return _viewModel;
}

- (UITableView *)mainTableView {

    if (!_mainTableView) {

        _mainTableView = [[UITableView alloc] init];
        _mainTableView.delegate = self;
        _mainTableView.dataSource = self;
        _mainTableView.backgroundColor = GX_BGCOLOR;
        _mainTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
        _mainTableView.tableHeaderView = self.listHeaderView;
        [_mainTableView registerClass:[LSCircleListTableCell class] forCellReuseIdentifier:[NSString stringWithUTF8String:object_getClassName([LSCircleListTableCell class])]];

        WS(weakSelf)
        _mainTableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{

            [weakSelf.viewModel.refreshDataCommand execute:nil];
        }];
        _mainTableView.mj_footer = [MJRefreshAutoNormalFooter footerWithRefreshingBlock:^{

            [weakSelf.viewModel.nextPageCommand execute:nil];
        }];
    }

    return _mainTableView;
}

- (LSCircleListHeaderView *)listHeaderView {

    if (!_listHeaderView) {

        _listHeaderView = [[LSCircleListHeaderView alloc] initWithViewModel:self.viewModel.listHeaderViewModel];
        _listHeaderView.frame = CGRectMake(0, 0, SCREEN_WIDTH, 160);
    }

    return _listHeaderView;
}

- (LSCircleListSectionHeaderView *)sectionHeaderView {

    if (!_sectionHeaderView) {

        _sectionHeaderView = [[LSCircleListSectionHeaderView alloc] initWithViewModel:self.viewModel.sectionHeaderViewModel];
    }

    return _sectionHeaderView;
}

#pragma mark - delegate

#pragma mark - UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {

    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

    return self.viewModel.dataArray.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {


    LSCircleListTableCell *cell = [tableView dequeueReusableCellWithIdentifier:[NSString stringWithUTF8String:object_getClassName([LSCircleListTableCell class])] forIndexPath:indexPath];

    if (self.viewModel.dataArray.count > indexPath.row) {

        cell.viewModel = self.viewModel.dataArray[indexPath.row];
    }

    return cell;
}

#pragma mark - UITableViewDelegate

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {

    return 100;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

    if (self.viewModel.dataArray.count > indexPath.row) {

        [self.viewModel.cellClickSubject sendNext:self.viewModel.dataArray[indexPath.row]];
    }
}

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {

    return self.sectionHeaderView;
}

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {

    return 45;
}

@end

3、LSCircleListModel的处理

//
//  LSCircleListModel.h
//  ZhongShui
//
//  Created by 王隆帅 on 16/3/17.
//  Copyright © 2016年 王隆帅. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface LSCircleListModel : NSObject

@property (nonatomic, copy) NSString *idStr;

@property (nonatomic, copy) NSString *title;

@property (nonatomic, copy) NSString *intro;

@property (nonatomic, copy) NSString *img;

@property (nonatomic, copy) NSString *memberCount;

@property (nonatomic, copy) NSString *topicCount;

@end
#import "LSCircleListModel.h"

@implementation LSCircleListModel

+ (NSDictionary *)mj_replacedKeyFromPropertyName {

    return  @{
              @"idStr":@"id",
              @"title":@"title",
              @"intro":@"intro",
              @"img":@"img",
              @"memberCount":@"MemberCount",
              @"topicCount":@"TopicCount",
              };
}

@end

4、ViewModel的处理

//
//  LSCircleListViewModel.h
//  ZhongShui
//
//  Created by 王隆帅 on 16/3/10.
//  Copyright © 2016年 王隆帅. All rights reserved.
//

#import "YCViewModel.h"
#import "LSCircleListHeaderViewModel.h"
#import "LSCircleListSectionHeaderViewModel.h"

@interface LSCircleListViewModel : YCViewModel

@property (nonatomic, strong) RACSubject *refreshEndSubject;

@property (nonatomic, strong) RACSubject *refreshUI;

@property (nonatomic, strong) RACCommand *refreshDataCommand;

@property (nonatomic, strong) RACCommand *nextPageCommand;

@property (nonatomic, strong) LSCircleListHeaderViewModel *listHeaderViewModel;

@property (nonatomic, strong) LSCircleListSectionHeaderViewModel *sectionHeaderViewModel;

@property (nonatomic, strong) NSArray *dataArray;

@property (nonatomic, strong) RACSubject *cellClickSubject;

@end
//
//  LSCircleListViewModel.m
//  ZhongShui
//
//  Created by 王隆帅 on 16/3/10.
//  Copyright © 2016年 王隆帅. All rights reserved.
//

#import "LSCircleListViewModel.h"
#import "LSCircleListCollectionCellViewModel.h"
#import "LSCircleListModel.h"

@interface LSCircleListViewModel ()

@property (nonatomic, assign) NSInteger currentPage;

@end

@implementation LSCircleListViewModel

- (void)yc_initialize {

    @weakify(self);
    [self.refreshDataCommand.executionSignals.switchToLatest subscribeNext:^(NSDictionary *dict) {

        @strongify(self);

        if (dict == nil) {

            [self.refreshEndSubject sendNext:@(LSRefreshError)];
            ShowErrorStatus(@"网络连接失败");
            return;
        }

        if ([dict[@"status"] integerValue] == 0) {

            self.listHeaderViewModel.dataArray = [[[([(NSDictionary *)dict[@"res"] arrayForKey:@"JoinCircles"]).rac_sequence map:^id(NSDictionary *dic) {

                LSCircleListModel *model = [LSCircleListModel mj_objectWithKeyValues:dic];
                LSCircleListCollectionCellViewModel *viewModel = [[LSCircleListCollectionCellViewModel alloc] init];
                viewModel.model = model;
                return viewModel;
            }] array] mutableCopy];

            self.dataArray = [[[([(NSDictionary *)dict[@"res"] arrayForKey:@"Circles"]).rac_sequence map:^id(NSDictionary *dic) {

                LSCircleListModel *model = [LSCircleListModel mj_objectWithKeyValues:dic];
                LSCircleListCollectionCellViewModel *viewModel = [[LSCircleListCollectionCellViewModel alloc] init];
                viewModel.model = model;
                return viewModel;
            }] array] mutableCopy];


            [self ls_setHeaderRefreshWithArray:dict[@"Circles"]];
            [self ls_dismiss];

        } else {

            [self.refreshEndSubject sendNext:@(LSRefreshError)];
            ShowMessage(dict[@"mes"]);
        }        
    }];


    [[[self.refreshDataCommand.executing skip:1] take:1] subscribeNext:^(id x) {

        @strongify(self);
        if ([x isEqualToNumber:@(YES)]) {

            [self ls_showWithStatus:@"正在加载"];
        }
    }];

    [self.nextPageCommand.executionSignals.switchToLatest subscribeNext:^(NSDictionary *dict) {

        @strongify(self);

        if (dict == nil) {

            [self.refreshEndSubject sendNext:@(LSRefreshError)];
            ShowErrorStatus(@"网络连接失败");
            return;
        }

        if ([dict[@"status"] integerValue] == 0) {

            NSMutableArray *recommandArray = [[NSMutableArray alloc] initWithArray:self.dataArray];
            for (NSDictionary *subDic in [(NSDictionary *)dict[@"res"] arrayForKey:@"Circles"]) {

                LSCircleListModel *model = [LSCircleListModel mj_objectWithKeyValues:subDic];
                LSCircleListCollectionCellViewModel *viewModel = [[LSCircleListCollectionCellViewModel alloc] init];
                viewModel.model = model;
                [recommandArray addObject:viewModel];
            }
            self.dataArray = recommandArray;

            [self ls_setFootRefreshWithArray:dict[@"Circles"]];
            [self ls_dismiss];

        } else {

            [self.refreshEndSubject sendNext:@(LSRefreshError)];
            ShowMessage(dict[@"mes"]);
        }
    }];
}

#pragma mark - private

- (NSMutableDictionary *)requestCircleListWithId:(NSString *)idStr currentPage:(NSString *)currentPage {

    idStr = IF_NULL_TO_STRING(idStr);
    currentPage = IF_NULL_TO_STRING(currentPage);

    NSMutableDictionary * dict = [@{@"MemberID": idStr, @"pageSize": LS_REQUEST_LIST_COUNT, @"pageIndex":currentPage} mutableCopy];

    return dict;
}

- (void)ls_setFootRefreshWithArray:(NSArray *)array {

    if (array.count < LS_REQUEST_LIST_NUM_COUNT) {

        [self.refreshEndSubject sendNext:@(LSFooterRefresh_HasNoMoreData)];
    } else {

        [self.refreshEndSubject sendNext:@(LSFooterRefresh_HasMoreData)];
    }
}

- (void)ls_setHeaderRefreshWithArray:(NSArray *)array {

    if (array.count < LS_REQUEST_LIST_NUM_COUNT) {

        [self.refreshEndSubject sendNext:@(LSHeaderRefresh_HasNoMoreData)];
    } else {

        [self.refreshEndSubject sendNext:@(LSHeaderRefresh_HasMoreData)];
    }
}

#pragma mark - lazyLoad
- (RACSubject *)refreshUI {

    if (!_refreshUI) {

        _refreshUI = [RACSubject subject];
    }

    return _refreshUI;
}

- (RACSubject *)refreshEndSubject {

    if (!_refreshEndSubject) {

        _refreshEndSubject = [RACSubject subject];
    }

    return _refreshEndSubject;
}

- (RACCommand *)refreshDataCommand {

    if (!_refreshDataCommand) {

        @weakify(self);
        _refreshDataCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {

            @strongify(self);
            return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {

                @strongify(self);
                self.currentPage = 1;
                [self.request POST:LS_URL_CIRCLE_MEMBER_LIST parameters:[self requestCircleListWithId:@"1" currentPage:[NSString stringWithFormat:@"%d",self.currentPage]] success:^(CMRequest *request, NSString *responseString) {

                    NSDictionary *dict = [responseString objectFromJSONString];
                    [subscriber sendNext:dict];
                    [subscriber sendCompleted];

                } failure:^(CMRequest *request, NSError *error) {

                    ShowErrorStatus(@"网络连接失败");
                    [subscriber sendCompleted];
                }];
                return nil;
            }];
        }];
    }

    return _refreshDataCommand;
}

- (RACCommand *)nextPageCommand {

    if (!_nextPageCommand) {

        @weakify(self);
        _nextPageCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {

            @strongify(self);
            return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {

                @strongify(self);
                self.currentPage ++;
                [self.request POST:LS_URL_CIRCLE_TOPIC_LIST parameters:nil success:^(CMRequest *request, NSString *responseString) {

                    NSDictionary *dict = [responseString objectFromJSONString];
                    [subscriber sendNext:dict];
                    [subscriber sendCompleted];

                } failure:^(CMRequest *request, NSError *error) {

                    @strongify(self);
                    self.currentPage --;
                    ShowErrorStatus(@"网络连接失败");
                    [subscriber sendCompleted];
                }];
                return nil;
            }];
        }];
    }

    return _nextPageCommand;
}

- (LSCircleListHeaderViewModel *)listHeaderViewModel {

    if (!_listHeaderViewModel) {

        _listHeaderViewModel = [[LSCircleListHeaderViewModel alloc] init];
        _listHeaderViewModel.title = @"已加入的圈子";
        _listHeaderViewModel.cellClickSubject = self.cellClickSubject;
    }

    return _listHeaderViewModel;
}

- (LSCircleListSectionHeaderViewModel *)sectionHeaderViewModel {

    if (!_sectionHeaderViewModel) {

        _sectionHeaderViewModel = [[LSCircleListSectionHeaderViewModel alloc] init];
        _sectionHeaderViewModel.title = @"推荐圈子";
    }

    return _sectionHeaderViewModel;
}

- (NSArray *)dataArray {

    if (!_dataArray) {

        _dataArray = [[NSArray alloc] init];
    }

    return _dataArray;
}

- (RACSubject *)cellClickSubject {

    if (!_cellClickSubject) {

        _cellClickSubject = [RACSubject subject];
    }

    return _cellClickSubject;
}

@end

ViewModel也是分为三个模块,由于代码太多摘重要的讲
ⅰ 第一个模块 : 处理数据、逻辑模块

处理数据这块,先用字典转为Model,在用Model配置ViewModel,ViewModel再去与UI及其逻辑对应。

ⅱ 第二个模块 : 私有函数

对于请求参数字典,可以放在VM中,便于模块化移植,也可以放在公共API中便于管理,看个人选择了,没有绝对的好位置,只有更适合个人的位置。

另外两个函数就是处理下拉及上拉时有没有更多数据的私有函数。

ⅲ 第三个模块 : 懒加载

五、后记

写到这里,我这个究级话痨也是累了,没错,这个设计模式就是话唠标准模式外加装逼标准模式,但是,也是你不得不会的。等你掌握后,那开发速度真是蹭蹭的,你的逻辑思维就好像每天在算10以内加减法。。话以至此,不懂得朋友们,欢迎百度和留言,大家多多交流,我是很喜欢交朋友滴,咱们一起好好学习敲代码,为了new出白富美。

六、满满的基情

感激我那迷人的小妖精~帅神的支持,由于本人比较懒,用了帅神的截图和点评,But,我们的目标都是希望大家共同进步,在我们基情的滋润下茁壮成长,咳咳咳...希望大家多多留言,多交流。喜欢的朋友欢迎关注,我会持续更新文章的,教你做一枚多愁善感的程序员,啊哈哈哈~亲们,记得点赞哦

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容