iOS MVVM+RAC 再不學(xué)你就OUT了


嚴(yán)肅的目錄
一救湖、前言
1.寫之前的廢話
二、MVVM和RAC理解
1.MVVM淺談
2.RAC淺談
3.兩者的結(jié)合運(yùn)用
三弊予、架構(gòu)部分
1.演示架構(gòu)詳解
2.基類說明
3.對(duì)上述吧啦吧啦幾句
四悦析、實(shí)戰(zhàn)部分
1.ViewController的處理
2.View的處理
3.Model的處理
4.ViewModel的處理
五、后記
1.程序員話癆模式開啟
六司光、基情感謝
1.程序員之間的滿滿基情

一琅坡、寫之前吧啦吧啦幾句

經(jīng)過第一篇文章的多愁善感,接下來是表演真正的絕技的時(shí)候了残家,回歸我的老本行榆俺,宇宙無敵iOS軟件開發(fā)工程師!(裝逼完畢坞淮,此處可以有點(diǎn)掌聲)最近的IT行情不太好了茴晋,很多小獅子們都找不到工作了,面試的時(shí)候回窘,也會(huì)經(jīng)常被問到MVC等設(shè)計(jì)模式和架構(gòu)诺擅,今天我就談?wù)勛约旱睦斫猓?dāng)然了啡直,我也是剛從小白變成小白發(fā)點(diǎn)黑烁涌,剛接手的公司的項(xiàng)目用到的是MVVM+RAC的框架,現(xiàn)在終于有那么點(diǎn)滴的了解了酒觅,希望能幫助你那么一點(diǎn)撮执,就是我的初衷了。設(shè)計(jì)模式這東西舷丹,說簡(jiǎn)單也難抒钱,說難也不難,嘎嘎嘎,總之萬(wàn)變不離其宗谋币,用到MVVM我們的目標(biāo)就是仗扬,讓VC這個(gè)胖子減減肥,代碼看起來顏值高點(diǎn)瑞信,維護(hù)的時(shí)候方便點(diǎn)厉颤,數(shù)據(jù)處理更清晰點(diǎn)穴豫,耦合度低一點(diǎn)凡简,可復(fù)用性高點(diǎn),獨(dú)立開發(fā)更簡(jiǎn)單點(diǎn).....也就是那么一點(diǎn)優(yōu)點(diǎn)啦精肃。廢話不多說秤涩,開始ing。

二司抱、MVVM和RAC理解

1.MVVM淺談

MVC是構(gòu)建iOS App的標(biāo)準(zhǔn)模式筐眷,是蘋果推薦的一個(gè)用來組織代碼的權(quán)威范式,市面上大部分App都是這樣構(gòu)建的习柠,具體組建模式不細(xì)說匀谣,iOS入門者都比較了解(雖然不一定能完全去遵守),但其幾個(gè)不能避免的問題卻是很嚴(yán)重困擾開發(fā)者比如厚重的ViewController资溃、遺失的網(wǎng)絡(luò)邏輯(沒有屬于它的位置)武翎、較差的可測(cè)試性等因此也就會(huì)有維護(hù)性較強(qiáng)、耦合性很低的一種新架構(gòu)MVVM (MVC 引申出得新的架構(gòu))的流行溶锭。

MVVM雖然來自微軟宝恶,但是不應(yīng)該反對(duì)它,它正式規(guī)范了正式規(guī)范了視圖和控制器緊耦合的性質(zhì)趴捅,如下圖:

MVVM1.png
MVVM2.png

寫到這里垫毙,我看見下面有同學(xué)??說,ViewModel是什么鬼拱绑?
View綁定到ViewModel综芥,然后執(zhí)行一些命令在向它請(qǐng)求一個(gè)動(dòng)作。而反過來猎拨,ViewModel跟Model通訊膀藐,告訴它更新來響應(yīng)UI。這樣便使得應(yīng)用構(gòu)建UI非常的容易迟几。相比較于MVC新引入的視圖模型消请。是視圖顯示邏輯、驗(yàn)證邏輯类腮、網(wǎng)絡(luò)請(qǐng)求等代碼存放的地方臊泰,唯一要注意的是,任何視圖本身的引用都不應(yīng)該放在VM中蚜枢,換句話說就是VM中不要引入U(xiǎn)IKit.h缸逃。

這樣针饥,首先解決了VC臃腫的問題,將邏輯代碼需频、網(wǎng)絡(luò)請(qǐng)求等都寫入了VM中丁眼,然后又由于VM中包含了所有的展示邏輯而且不會(huì)引用V,所以它是可以通過編程充分測(cè)試的昭殉。

2.RAC淺談

ReactiveCocoa是響應(yīng)式編程(FRP)在iOS中的一個(gè)實(shí)現(xiàn)框架苞七,它的開源地址為:https://github.com/ReactiveCocoa/ReactiveCocoa# ;在網(wǎng)上看了幾篇文章挪丢,感覺理論講了很多蹂风,但是代碼還是看不太懂,于是自己把它github文檔上的一些使用的經(jīng)典示例實(shí)現(xiàn)了一下乾蓬,項(xiàng)目中有需要時(shí)可以直接搬過去用惠啄,用的熟練了再讀源碼也比較容易理解。

RAC雖然最大的優(yōu)點(diǎn)是提供了一個(gè)單一的任内、統(tǒng)一的方法去處理異步的行為撵渡,包括delegate方法,blocks回調(diào),target-action機(jī)制,notifications和KVO.但是不要簡(jiǎn)單的只是單純的認(rèn)為他僅僅就是減少代碼復(fù)雜度,更好的配合MVVM而已死嗦,小伙子趋距,這樣你就小看它了。
它最大的與眾不同是提供了一種新的寫代碼的思維越走,**由于RAC將Cocoa中KVO棚品、UIKit event、delegate廊敌、selector等都增加了RAC支持铜跑,所以都不用去做很多跨函數(shù)的事。

這里我就不細(xì)說了骡澈,里面的東西還是很多滴锅纺,需要慢慢沉淀,這里我推薦一篇寫的比較好的教程肋殴,大家互相學(xué)習(xí)囤锉。http://www.reibang.com/p/87ef6720a096

3.兩者結(jié)合的應(yīng)用

在此次介紹中,會(huì)使用MVVM+RAC結(jié)合的方式护锤,搞定一個(gè)添加上拉加載及下拉刷新的列表官地,所以更多的詮釋MVVM思想,而不是RAC的邏輯鏈?zhǔn)讲僮鳎ㄟ@一點(diǎn)用登錄界面來寫更能體現(xiàn)YoY )烙懦,RAC在此扮演的更大一部分的角色是更好的解耦驱入,減少代碼復(fù)雜度,使代碼層次分明、邏輯清晰更便于維護(hù)升級(jí)亏较。

二莺褒、架構(gòu)部分

1.演示架構(gòu)詳解

廢話不說半句多,原諒我這么不愛說話雪情,啊啊啊啊遵岩,看圖!


架構(gòu)圖.png

Frameworks
存放系統(tǒng)庫(kù)的文件夾巡通,新建工程的時(shí)候自己創(chuàng)建一個(gè)這個(gè)名字的文件夾尘执,添加的系統(tǒng)庫(kù)就會(huì)乖乖進(jìn)來啦??

AppDelegate
誰(shuí)不知道這是干嘛的,你過來扁达,我保證請(qǐng)你吃肉

Cl0ass
工程的主體正卧,日常代碼就放到這里了

General : 通用類(文件夾項(xiàng)目移植過程中都不需要更改的就能直接使用的)Base : 基類 (整個(gè)框架的基類)
Categories : 公共擴(kuò)展類 (就是一些常用的類別,比如分享啊什么的)
Core : 公共核心類(一般存放個(gè)人信息跪解、接口API等)
Models : 公共Model (公用的一些數(shù)據(jù)模型)
Views : 公共View (封裝的一些常用的View)

Helpers : 工程的相關(guān)輔助類(比如類似數(shù)據(jù)請(qǐng)求、表單上傳签孔、網(wǎng)絡(luò)監(jiān)測(cè)等工具類)
** Macro :** 宏定義類
** Sections :** 各模塊的文件夾(一般而言叉讥,我們以人為單位,畢竟項(xiàng)目很多時(shí)候是多人開發(fā)饥追,每個(gè)人創(chuàng)建一個(gè)Sections互不影響图仓,里面就存放自己的所有代碼和頁(yè)面啦)
Vendors : 第三方的類庫(kù)/SDK,如UMeng但绕、WeiboSDK救崔、WeixinSDK等等。

Resource
這里放置的是工程所需的一些資源捏顺,如下
Fonts 字體
Images 圖片
Sounds 聲音
Videos 視頻

Pods
當(dāng)你開發(fā)iOS應(yīng)用時(shí)六孵,會(huì)經(jīng)常使用到很多第三方開源類庫(kù),比如JSONKit幅骄,AFNetWorking等等劫窒。可能某個(gè)類庫(kù)又用到其他類庫(kù)拆座,所以要使用它主巍,必須得另外下載其他類庫(kù),而其他類庫(kù)又用到其他類庫(kù)挪凑,“子子孫孫無窮盡也”孕索,反正在早期我是體會(huì)過這種痛苦,好心酸躏碳,手動(dòng)一個(gè)個(gè)去下載所需類庫(kù)是十分麻煩的搞旭。

還有另外一種常見情況是,你項(xiàng)目中用到的類庫(kù)有更新,你必須得重新下載新版本选脊,重新加入到項(xiàng)目中杭抠,十分麻煩。
CocoaPods就是幫你解決上面的問題的恳啥,話說這玩意應(yīng)該是iOS最常用最有名的類庫(kù)管理工具了偏灿,作為iOS程序員的我們,掌握CocoaPods的使用是必不可少的基本技能了钝的,至于這玩意該咋用翁垂?

6666

嘎嘎嘎,如果你看的足夠細(xì)心硝桩,給你們點(diǎn)福利沿猜,這個(gè)網(wǎng)站可以學(xué)習(xí)哈,哎碗脊,我心軟 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 : 設(shè)置導(dǎo)航欄衙伶、分欄

yd_getNewData : 初次獲取數(shù)據(jù)的時(shí)候調(diào)用(不是特別必要)

2.YDView

yd_setupViews : 添加子View到主View
yd_bindViewModel : 綁定V與VM
yd_addReturnKeyBoard : 設(shè)置點(diǎn)擊空白鍵盤回收

3.YDViewModel

yc_initialize : 進(jìn)行一些邏輯綁定祈坠,網(wǎng)絡(luò)數(shù)據(jù)請(qǐng)求處理。
LSRefreshDataStatus 數(shù)據(jù)處理后需要進(jìn)行的操作標(biāo)識(shí)LSHeaderRefresh_HasMoreData 下拉還有更多數(shù)據(jù)
LSHeaderRefresh_HasNoMoreData 下拉沒有更多數(shù)據(jù)
LSFooterRefresh_HasMoreData 上拉還有更多數(shù)據(jù)
LSFooterRefresh_HasNoMoreData 上拉沒有更多數(shù)據(jù)
LSRefreshError 刷新出錯(cuò)
LSRefreshUI 僅僅刷新UI布局

3.對(duì)上述吧啦吧啦幾句

基類的作用是統(tǒng)一管理矢劲,統(tǒng)一風(fēng)格赦拘,便于編碼,有更多的額外的附加功能的話芬沉,建議使用Protocol 或 Category躺同,這樣移植性強(qiáng),便于管理與擴(kuò)展丸逸,不至于牽一發(fā)而動(dòng)全身蹋艺。
本篇基類核心是用VM來配置V(VC),并提供一些必須的Protocol方法來處理界面顯示椭员、邏輯车海,將代碼風(fēng)格規(guī)范化,各個(gè)部分的功能明朗化隘击,這樣侍芝,當(dāng)你需要寫什么,需要找什么埋同,需要更改什么的時(shí)候都會(huì)很明確這些代碼的位置州叠,邏輯更清晰,而不會(huì)浪費(fèi)更多的時(shí)間在思考應(yīng)該寫在哪凶赁,該去哪找咧栗,要改的地方在哪這種不該費(fèi)時(shí)間的問題上逆甜。

四、實(shí)戰(zhàn)部分

接下來致板,實(shí)現(xiàn)一個(gè)最基本的列表交煞,由于本人比較懶,用我小伙伴的demo講解下斟或。話不多說素征,半句多,嘎嘎嘎萝挤,啦啦啊御毅。

列表圖片.png

然后處理完后的目錄如下

目錄結(jié)構(gòu).png

簡(jiǎn)單介紹一下:
ViewController****LSCircleListViewController : 界面主控制器,負(fù)責(zé)跳轉(zhuǎn)怜珍、Navgation端蛆、TabBar等

View****LSCircleListView : 界面主View,負(fù)責(zé)主要界面的顯示
LSCircleListHeaderView : 頭部Header酥泛,封裝的內(nèi)部含有一個(gè)CollectionView
LSCircleListCollectionCell : 頭部Header中的CollectionView自定義的Cell
LSCircleListSectionHeaderView : SectionView今豆,此界面不需復(fù)用,所以單純一個(gè)View即可揭璃,若需要復(fù)用需要TableViewHeaderFooterView
LSCircleListTableCell : 主TableView的Cell

ViewModel****LSCircleListViewModel : 界面主ViewModel
LSCircleListHeaderViewModel : 頭部Header對(duì)應(yīng)的ViewModel
LSCircleListCollectionCellViewModel : 頭部CollectionCell及TableViewCell的ViewModel(因?yàn)槎叩臄?shù)據(jù)結(jié)構(gòu)是一致的)
LSCircleListSectionHeaderViewModel : Section的ViewModel

Model****LSCircleListModel : 圈子的數(shù)據(jù)模型(header和tableViewCell數(shù)據(jù)結(jié)構(gòu)是一致的)

一個(gè)小小的界面這么多類...是不是難以接受了晚凿,淡定些,騷年瘦馍!你要想想把這些個(gè)東西都放在VC內(nèi)是個(gè)什么趕腳?也得好幾千行呢Sσ邸(有點(diǎn)夸張情组!不過也夠頭疼的),這么多類箩祥,這里著重講一下主VC院崇、主V、主VM袍祖、主M就ok底瓣,能詳細(xì)講明白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

對(duì)于VC捐凭,分為三個(gè)模塊页畦,下面分別來說一下:

i 第一個(gè)模塊:系統(tǒng)函數(shù)


此函數(shù)是從iOS6.0開始在ViewController中新增一個(gè)更新約束布局的方法御板,這個(gè)方法默認(rèn)的實(shí)現(xiàn)是調(diào)用對(duì)應(yīng)View的updateConstraints
。ViewController的View在更新視圖布局時(shí)御蒲,會(huì)先調(diào)用ViewController的updateViewConstraints 方法缩举。我們可以通過重寫這個(gè)方法去更新當(dāng)前View的內(nèi)部布局垦梆,而不用再繼承這個(gè)View去重寫-updateConstraints方法匹颤。我們?cè)谥貙戇@個(gè)方法時(shí),務(wù)必要調(diào)用 super 或者 調(diào)用當(dāng)前View的 -updateConstraints 方法托猩。

ⅱ 第二個(gè)模塊 : 私有函數(shù)

前面基類內(nèi)也提到了這三個(gè)函數(shù)的具體作用印蓖,即
yd_addSubviews : 添加View到ViewController

yd_bindViewModel : 這里綁定了兩個(gè)跳轉(zhuǎn)事件。

yd_layoutNavigation : 設(shè)置了標(biāo)題為“圈子列表”京腥、及TabBar不隱藏

ⅲ 第三個(gè)模塊 : 懶加載

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(@"網(wǎng)絡(luò)連接失敗");
            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(@"網(wǎng)絡(luò)連接失敗");
            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(@"網(wǎng)絡(luò)連接失敗");
                    [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(@"網(wǎng)絡(luò)連接失敗");
                    [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也是分為三個(gè)模塊摆尝,由于代碼太多摘重要的講
ⅰ 第一個(gè)模塊 : 處理數(shù)據(jù)、邏輯模塊

處理數(shù)據(jù)這塊因悲,先用字典轉(zhuǎn)為Model堕汞,在用Model配置ViewModel,ViewModel再去與UI及其邏輯對(duì)應(yīng)晃琳。

ⅱ 第二個(gè)模塊 : 私有函數(shù)

對(duì)于請(qǐng)求參數(shù)字典讯检,可以放在VM中,便于模塊化移植卫旱,也可以放在公共API中便于管理人灼,看個(gè)人選擇了,沒有絕對(duì)的好位置顾翼,只有更適合個(gè)人的位置投放。

另外兩個(gè)函數(shù)就是處理下拉及上拉時(shí)有沒有更多數(shù)據(jù)的私有函數(shù)。

ⅲ 第三個(gè)模塊 : 懶加載

五适贸、后記

寫到這里灸芳,我這個(gè)究級(jí)話癆也是累了,沒錯(cuò)拜姿,這個(gè)設(shè)計(jì)模式就是話嘮標(biāo)準(zhǔn)模式外加裝逼標(biāo)準(zhǔn)模式烙样,但是,也是你不得不會(huì)的蕊肥。等你掌握后谒获,那開發(fā)速度真是蹭蹭的,你的邏輯思維就好像每天在算10以內(nèi)加減法壁却。批狱。話以至此,不懂得朋友們儒洛,歡迎百度和留言精耐,大家多多交流,我是很喜歡交朋友滴琅锻,咱們一起好好學(xué)習(xí)敲代碼,為了new出白富美卦停。

六向胡、滿滿的基情

感激我那迷人的小妖精~帥神的支持,由于本人比較懶惊完,用了帥神的截圖和點(diǎn)評(píng)僵芹,But,我們的目標(biāo)都是希望大家共同進(jìn)步小槐,在我們基情的滋潤(rùn)下茁壯成長(zhǎng)拇派,咳咳咳...希望大家多多留言,多交流凿跳。喜歡的朋友歡迎關(guān)注件豌,我會(huì)持續(xù)更新文章的,教你做一枚多愁善感的程序員控嗜,啊哈哈哈~親們茧彤,記得點(diǎn)贊哦

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市疆栏,隨后出現(xiàn)的幾起案子曾掂,更是在濱河造成了極大的恐慌,老刑警劉巖壁顶,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件珠洗,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡若专,警方通過查閱死者的電腦和手機(jī)许蓖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來调衰,“玉大人蛔糯,你說我怎么就攤上這事〗咽剑” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵动壤,是天一觀的道長(zhǎng)萝喘。 經(jīng)常有香客問我,道長(zhǎng)琼懊,這世上最難降的妖魔是什么阁簸? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮哼丈,結(jié)果婚禮上启妹,老公的妹妹穿的比我還像新娘。我一直安慰自己醉旦,他們只是感情好饶米,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布桨啃。 她就那樣靜靜地躺著,像睡著了一般檬输。 火紅的嫁衣襯著肌膚如雪照瘾。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天丧慈,我揣著相機(jī)與錄音析命,去河邊找鬼。 笑死逃默,一個(gè)胖子當(dāng)著我的面吹牛鹃愤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播完域,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼软吐,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了筒主?” 一聲冷哼從身側(cè)響起关噪,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎乌妙,沒想到半個(gè)月后使兔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡藤韵,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年虐沥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泽艘。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡欲险,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出匹涮,到底是詐尸還是另有隱情天试,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布然低,位于F島的核電站喜每,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏雳攘。R本人自食惡果不足惜带兜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望吨灭。 院中可真熱鬧刚照,春花似錦、人聲如沸喧兄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至檩互,卻和暖如春特幔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背闸昨。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工蚯斯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人饵较。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓拍嵌,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親循诉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子横辆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容