一、前言
對于設(shè)計模式這個讓人又愛又恨的玩意积糯,說來其實簡單掂墓,但一千個人眼中就有一千種哈姆雷特,說他千變?nèi)f化確實是事實看成,而且當(dāng)你深入其中的時候你真的會上癮君编,并樂此不疲!
二川慌、談?wù)凪VVM和RAC
1吃嘿、MVVM淺析
到這里我就默認(rèn)你看過MVVM相關(guān)文章(畢竟相關(guān)文章已經(jīng)可以用滿天飛來形容了~(≧▽≦)/~啦啦啦l裟恕),僅僅簡要談?wù)勎覍ζ涞睦斫狻?/p>
MVC是構(gòu)建iOS App的標(biāo)準(zhǔn)模式兑燥,是蘋果推薦的一個用來組織代碼的權(quán)威范式亮瓷,市面上大部分App都是這樣構(gòu)建的,具體組建模式不細(xì)說降瞳,iOS入門者都比較了解(雖然不一定能完全去遵守)嘱支,但其幾個不能避免的問題卻是很嚴(yán)重困擾開發(fā)者比如厚重的ViewController、遺失的網(wǎng)絡(luò)邏輯(沒有屬于它的位置)挣饥、較差的可測試性等因此也就會有維護性較強除师、耦合性很低的一種新架構(gòu)MVVM (MVC 引申出得新的架構(gòu))的流行。
MVVM雖然來自微軟扔枫,但是不應(yīng)該反對它汛聚,它正式規(guī)范了正式規(guī)范了視圖和控制器緊耦合的性質(zhì),如下圖:
MVVM圖示
ViewModel: 相比較于MVC新引入的視圖模型茧吊。是視圖顯示邏輯贞岭、驗證邏輯、網(wǎng)絡(luò)請求等代碼存放的地方搓侄,唯一要注意的是,任何視圖本身的引用都不應(yīng)該放在VM中话速,換句話說就是VM中不要引入UIKit.h (對于image這個讶踪,也有人將其看做數(shù)據(jù)來處理,這就看個人想法了泊交,并不影響整體的架構(gòu))乳讥。
這樣,首先解決了VC臃腫的問題廓俭,將邏輯代碼云石、網(wǎng)絡(luò)請求等都寫入了VM中,然后又由于VM中包含了所有的展示邏輯而且不會引用V研乒,所以它是可以通過編程充分測試的汹忠。
2、RAC淺淺析
特別淺雹熬。宽菜。。本文重點是框架及實戰(zhàn)及MVVM思想竿报,RAC這玩意話說學(xué)習(xí)曲線較長铅乡,難以理解,不好上手烈菌,是因為之前學(xué)習(xí)的時候使用者阵幸、中文教程還比較少花履,所以學(xué)習(xí)運用起來比較費勁,(當(dāng)時確實廢了好大得勁挚赊,實力裝逼一把 @%&$%& )但現(xiàn)在已經(jīng)成熟的爛大街了诡壁,只要有心,好的教程一大把咬腕,能潛下心來看我寫的水文的人欢峰,拿下RAC不在話下!
ReactiveCocoa 可以說是結(jié)合了函數(shù)式編程和響應(yīng)式編程的框架涨共,也可稱其為函數(shù)響應(yīng)式編程(FRP)框架纽帖,強調(diào)一點,RAC雖然最大的優(yōu)點是提供了一個單一的举反、統(tǒng)一的方法去處理異步的行為懊直,包括delegate方法,blocks回調(diào),target-action機制,notifications和KVO.但是不要簡單的只是單純的認(rèn)為他僅僅就是減少代碼復(fù)雜度,更好的配合MVVM而已火鼻,小伙子室囊,這樣你就小看它了。
它最大的與眾不同是提供了一種新的寫代碼的思維魁索,由于RAC將Cocoa中KVO融撞、UIKit event、delegate粗蔚、selector等都增加了RAC支持尝偎,所以都不用去做很多跨函數(shù)的事。
如果全工程都使用RAC來實現(xiàn)鹏控,對于同一個業(yè)務(wù)邏輯終于可以在同一塊代碼里完成了致扯,將UI事件,邏輯處理当辐,文件或數(shù)據(jù)庫操作抖僵,異步網(wǎng)絡(luò)請求,UI結(jié)果顯示缘揪,這一大套統(tǒng)統(tǒng)用函數(shù)式編程的思路嵌套起來耍群,進入頁面時搭建好這所有的關(guān)系,用戶點擊后妥妥的等著這一套聯(lián)系一個個的按期望的邏輯和次序觸發(fā)寺晌,最后顯示給用戶世吨。
3、本篇對兩者的理解運用
在此次介紹中呻征,會使用MVVM+RAC結(jié)合的方式耘婚,搞定一個添加上拉加載及下拉刷新的列表,所以更多的詮釋MVVM思想陆赋,而不是RAC的邏輯鏈?zhǔn)讲僮鳎ㄟ@一點用登錄界面來寫更能體現(xiàn)Y^o^Y )沐祷,RAC在此扮演的更大一部分的角色是更好的解耦嚷闭,減少代碼復(fù)雜度,使代碼層次分明赖临、邏輯清晰更便于維護升級胞锰。
二、框架部分
1兢榨、框架目錄詳解
首先介紹一下本框架的目錄結(jié)構(gòu)嗅榕,如下圖
1、Frameworks
存放系統(tǒng)庫的虛擬文件夾,目前搭建框架的時候需要手動添加一個名稱為Frameworks的虛擬文件夾,這樣你在Build Phases 中添加的系統(tǒng)庫會自動歸入此文件夾吵聪,不會直接在外部顯示以至于打亂目錄結(jié)構(gòu)凌那。系統(tǒng)庫添加流程如下:
另外,細(xì)心地家伙會發(fā)現(xiàn)此目錄中有兩個相同的Frameworks, 那這到底是什么鬼?最上面的那個Frameworks是在自己搭框架自己添加的,當(dāng)時的項目還很單純, 沒有這么淘氣吟逝,問題出在下面那個Pods Target上帽蝶,添加它之后就會自動給你生成一個虛擬的Frameworks的文件夾。
既然提到了Pods块攒,那接下來講講CocoaPods(第三方類庫管理工具)励稳。
2、CocoaPods
當(dāng)你開發(fā)iOS應(yīng)用時囱井,會經(jīng)常使用到很多第三方開源類庫驹尼,比如JSONKit,AFNetWorking等等庞呕》鲂溃可能某個類庫又用到其他類庫,所以要使用它千扶,必須得另外下載其他類庫,而其他類庫又用到其他類庫骆捧,“子子孫孫無窮盡也”澎羞,手動一個個去下載所需類庫是十分麻煩的。
還有另外一種常見情況是敛苇,你項目中用到的類庫有更新妆绞,你必須得重新下載新版本,重新加入到項目中枫攀,十分麻煩括饶。
CocoaPods就是幫你解決上面的問題的,話說這玩意應(yīng)該是iOS最常用最有名的類庫管理工具了来涨,作為iOS程序員的我們图焰,掌握CocoaPods的使用是必不可少的基本技能了。
3蹦掐、AppDelegate
這個目錄下放的是AppDelegate.h(.m)文件技羔,是整個應(yīng)用的入口文件僵闯,所以單獨拿出來。一會兒告訴你如何寫一個簡潔的AppDelegate藤滥,會在這個文件夾里添加一些類鳖粟,所以將其放入一個文件夾內(nèi)還是很有必要的。
4拙绊、Class
工程主體類, 日常大部分開發(fā)代碼均在這里,又細(xì)分了好多次級目錄向图。
通用類
General :通用類(文件夾項目移植過程中都不需要更改的就能直接使用的)
Base :基類 (整個框架的基類)
Categories :公共擴展類 (就是一些常用的類別,比如分享啊什么的)
Core :公共核心類(一般存放個人信息标沪、接口API等)
Models :公共Model (公用的一些數(shù)據(jù)模型)
Views :公共View (封裝的一些常用的View)
工具類
Helpers :工程的相關(guān)輔助類(比如類似數(shù)據(jù)請求榄攀、表單上傳、網(wǎng)絡(luò)監(jiān)測等工具類)
宏定義類
Macro :宏定義類 (就是整個應(yīng)用會用到的宏定義)
AppMacro.happ項目的相關(guān)宏定義
NotificationMacro.h通知相關(guān)的宏定義
VendorMacro.h第三方相關(guān)宏定義
UtilsMacro.h為簡化代碼的宏定義
...等等等等(其他隨你定啦谨娜!Y^o^Y )
APP具體模塊代碼類
Sections :各模塊的文件夾(一般而言航攒,我們以人為單位)
LSSections
CLSections
...等等等等
每個成員的文件夾下是其所負(fù)責(zé)模塊的文件夾,如下(接著上面的個人文件夾):
PHP :模塊名趴梢,也可以是首頁(HomePage)...等等
ViewControllers界面控制器存放處(這是文件夾名)
ViewModels打雜的(MVVM的核心漠畜、解耦合、處理邏輯等)
Views界面相關(guān)View存放處(界面相關(guān)子View)
Models數(shù)據(jù)模型存放處(各種單純的數(shù)據(jù)模型坞靶,一點都不胖憔狞,是標(biāo)準(zhǔn)的瘦Model)
這就是標(biāo)準(zhǔn)的MVVM了。彰阴。瘾敢。為啥不和上面目錄連起來呢?為啥呢尿这?為啥呢簇抵?因為臣妾做不到啊I渲凇5凇!(不會三級叨橱、四級列表的MarkDown寫法典蜕,求大神支招!良辰必有重謝B尴础)
第三方類庫
Vendors :第三方的類庫/SDK愉舔,如UMeng、WeiboSDK伙菜、WeixinSDK等等轩缤。
到這哥們又該疑惑了,心里該碎碎念了:(????д????)????? What are you 弄啥嘞!剛才剛講了個第三方庫管理CocoaPods典奉,你丫這里自己又搞了一個躺翻,?( ˉ?? ˉ?) 信不信我突突了你!
哈哈哈卫玖,剛才的CocoaPods確實管理著大部分的第三方庫公你,這里建立第三方庫目錄的原因有兩個:其一,并不是所有的你需要的第三方都支持pods的假瞬,所以還是需要手動添加一些類庫陕靠。其二,一些第三方庫雖然支持pods脱茉,但是需要我們?nèi)ジ纳踔磷远x這個第三方剪芥,此時也需要放入這里,也防止使用pods一不小心更新掉你的自定義琴许!?(?)? 你來打我八胺尽!
5榜田、Resource
這里放置的是工程所需的一些資源益兄,如下
Fonts字體
Images圖片(當(dāng)然你可以添加至Assets.xcassets, 沒人攔著你)
Sounds聲音
Videos視頻
ok,目錄就講到這里箭券!想知道更詳細(xì)的可以私信我净捅!
2、基類詳解
這里著重講解一下VC辩块、V蛔六、VM的基類,其他的模式與View類似所以略過废亭,其中TableViewCell的基類稍微特殊所以也提一下国章。
我目前的基類如下圖:
1、YDViewController
由于Cell比較特殊豆村,所以單拎出來說一下捉腥。觀察上面的ViewMdoel、View等的基類會發(fā)現(xiàn)每個基類都會有數(shù)據(jù)綁定的地方你画,但是cell得數(shù)據(jù)綁定需要放在數(shù)據(jù)初始化的時候,因為所有的基類的數(shù)據(jù)邏輯綁定都是在沒有返回初始化對象的時候調(diào)用的桃漾,但是cell中假如在那里面進行數(shù)據(jù)綁定會出現(xiàn)問題比如下圖:
上圖中的函數(shù)假如是在 bindViewModel 內(nèi)坏匪,則會復(fù)用失敗,點擊按鈕是沒有反應(yīng)的撬统,但是假如是在數(shù)據(jù)初始化的時候調(diào)用:比如 setViewModel 的時候适滓,就會OK了,因為里面用到了cell的在RAC中復(fù)用機制rac_prepareForReuseSignal恋追,在cell還沒有初始化返回的時候是失效的凭迹。
3罚屋、題外話
基類的作用是統(tǒng)一管理,統(tǒng)一風(fēng)格嗅绸,便于編碼脾猛,有更多的額外的附加功能的話,建議使用Protocol 或 Category鱼鸠,這樣移植性強猛拴,便于管理與擴展,不至于牽一發(fā)而動全身蚀狰。
本篇基類核心是用VM來配置V(VC)愉昆,并提供一些必須的Protocol方法來處理界面顯示、邏輯麻蹋,將代碼風(fēng)格規(guī)范化跛溉,各個部分的功能明朗化,這樣扮授,當(dāng)你需要寫什么芳室,需要找什么,需要更改什么的時候都會很明確這些代碼的位置糙箍,邏輯更清晰渤愁,而不會浪費更多的時間在思考應(yīng)該寫在哪,該去哪找深夯,要改的地方在哪這種不該費時間的問題上抖格。
三、實戰(zhàn)部分(經(jīng)典列表的實現(xiàn))
這里講一下如下界面的代碼構(gòu)造方式咕晋,很普通的一個列表:(懶得再寫了雹拄,這是我之前做的一個項目的一個界面,之前基類講解中會看到都是YD開頭的掌呜,在這里是YC開頭就這個區(qū)別而已)
首先觀察這個界面滓玖,需求是:頭部的內(nèi)容數(shù)量多的話是可以左右滑動的,然后整體是可以上拉加載的质蕉。我是這樣處理的:首先界面整體是一個TableView势篡,然后分為一個Header、一個Section和主體列表Row模暗。在Header上嵌套一個CollectionView保證可復(fù)用禁悠。具體分層如下
然后處理完后的目錄如下:
簡單介紹一下:
ViewController
LSCircleListViewController :界面主控制器,負(fù)責(zé)跳轉(zhuǎn)兑宇、Navgation碍侦、TabBar等
View
LSCircleListView :界面主View,負(fù)責(zé)主要界面的顯示
LSCircleListHeaderView :頭部Header,封裝的內(nèi)部含有一個CollectionView
LSCircleListCollectionCell :頭部Header中的CollectionView自定義的Cell
LSCircleListSectionHeaderView :SectionView瓷产,此界面不需復(fù)用站玄,所以單純一個View即可,若需要復(fù)用需要TableViewHeaderFooterView
LSCircleListTableCell :主TableView的Cell
ViewModel
LSCircleListViewModel :界面主ViewModel
LSCircleListHeaderViewModel :頭部Header對應(yīng)的ViewModel
LSCircleListCollectionCellViewModel :頭部CollectionCell及TableViewCell的ViewModel(因為二者的數(shù)據(jù)結(jié)構(gòu)是一致的)
LSCircleListSectionHeaderViewModel :Section的ViewModel
Model
LSCircleListModel :圈子的數(shù)據(jù)模型(header和tableViewCell數(shù)據(jù)結(jié)構(gòu)是一致的)
一個小小的界面這么多類...是不是難以接受了濒旦,淡定些株旷,騷年!你要想想把這些個東西都放在VC內(nèi)是個什么趕腳疤估?也得好幾千行呢T殖!(有點夸張!不過也夠頭疼的)铃拇,這么多類钞瀑,這里著重講一下主VC、主V慷荔、主VM雕什、主M就ok,能詳細(xì)講明白MVVM之間是如何工作的就一通百通了显晶。
1贷岸、LSCircleListViewController的處理
先上代碼:
#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 第一個模塊:系統(tǒng)函數(shù)
此函數(shù)是從iOS6.0開始在ViewController中新增一個更新約束布局的方法偿警,這個方法默認(rèn)的實現(xiàn)是調(diào)用對應(yīng)View的 updateConstraints 移国。ViewController的View在更新視圖布局時酥诽,會先調(diào)用ViewController的updateViewConstraints 方法吸祟。我們可以通過重寫這個方法去更新當(dāng)前View的內(nèi)部布局擒贸,而不用再繼承這個View去重寫-updateConstraints方法。我們在重寫這個方法時誉结,務(wù)必要調(diào)用 super 或者 調(diào)用當(dāng)前View的 -updateConstraints 方法董虱。
ⅱ 第二個模塊 : 私有函數(shù)
前面基類內(nèi)也提到了這三個函數(shù)的具體作用店煞,即
yd_addSubviews :添加View到ViewController
yd_bindViewModel :這里綁定了兩個跳轉(zhuǎn)事件苞慢。
yd_layoutNavigation :設(shè)置了標(biāo)題為“圈子列表”诵原、及TabBar不隱藏
ⅲ 第三個模塊 : 懶加載
這就不用解釋了,用到時再加載挽放。
2绍赛、View的處理
先上代碼
#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
主View分為四個模塊:
ⅰ 第一個模塊 : 系統(tǒng)函數(shù)
每個View都會有對應(yīng)的ViewModel,這樣也更易復(fù)用辑畦,這里因為是主View惹资,一般而言我都會使得VC和主V共用一個VM,這樣對于跳轉(zhuǎn)航闺、數(shù)據(jù)共享等都有著極大的好處。
ⅱ 第二個模塊 : 私有函數(shù)
具體作用途中已經(jīng)標(biāo)注,需要注意的是這些對于不同數(shù)據(jù)的處理潦刃,是我自己寫的侮措,邏輯上肯定沒有那么縝密,僅供參考乖杠。
ⅲ 第三個模塊 : 懶加載
這里沒啥好說的分扎,就是用的MJRefresh這個第三方庫做的刷新。不過胧洒,假如你細(xì)心的話肯定會發(fā)現(xiàn)下面那兩個View都是用VM來配置初始化的畏吓,這個和主View的配置初始化的意義是一樣的。
ⅳ 第四個模塊 : 代理及數(shù)據(jù)源
其中使用的是自定義Cell卫漫,用ViewModel來配置菲饼,點擊事件也是和之前的VC的跳轉(zhuǎn)聯(lián)系起來了,并將VM傳過去列赎。
3宏悦、LSCircleListModel的處理
同樣,先上代碼
#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
這個就不貼圖介紹了包吝,就是單純的數(shù)據(jù)模型饼煞,使用了MJExtention這個數(shù)據(jù)模型轉(zhuǎn)換框架。沒有做任何其他的邏輯處理诗越。
4砖瞧、ViewModel的處理
#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
#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也是分為三個模塊,由于代碼太多摘重要的講
ⅰ 第一個模塊 : 處理數(shù)據(jù)嚷狞、邏輯模塊
處理數(shù)據(jù)這塊块促,先用字典轉(zhuǎn)為Model,在用Model配置ViewModel感耙,ViewModel再去與UI及其邏輯對應(yīng)褂乍。
ⅱ 第二個模塊 : 私有函數(shù)
對于請求參數(shù)字典,可以放在VM中即硼,便于模塊化移植逃片,也可以放在公共API中便于管理,看個人選擇了只酥,沒有絕對的好位置褥实,只有更適合個人的位置。
另外兩個函數(shù)就是處理下拉及上拉時有沒有更多數(shù)據(jù)的私有函數(shù)裂允。
ⅲ 第三個模塊 : 懶加載
此數(shù)據(jù)請求用的是AFNetworking损离。
5、APPDelegate的代碼簡化
一般而言绝编,我們正式項目中會遇到很多需要啟動項目時就加載的僻澎,所以很快APPDelegate就會越來越龐大貌踏,既然其他的代碼都簡化解耦了,這里也可以做一下處理窟勃。
目錄如下:
簡化后的AppDelegate如下:
其他代碼存放的位置如下:
當(dāng)類對象被引入項目時, runtime 會向每一個類對象發(fā)送 load 消息. load 方法還是非常的神奇的, 因為它會在每一個類甚至分類被引入時僅調(diào)用一次, 調(diào)用的順序是父類優(yōu)先于子類, 子類優(yōu)先于分類. 而且 load 方法不會被類自動繼承, 每一個類中的 load 方法都不需要像 viewDidLoad 方法一樣調(diào)用父類的方法祖乳。
這是利用了這個算是黑魔法的玩意,哈哈秉氧,就簡化了APPDelegate眷昆!
四、后記
當(dāng)初本來想干掉基類來著汁咏,想利用Category + Protocol并利用Runtime的Methode Swizzle 來給系統(tǒng)函數(shù)添加自己的私有函數(shù)亚斋,當(dāng)初VC已經(jīng)搞定了,然而發(fā)現(xiàn)這樣牽涉面太廣攘滩,你對VC做了Category帅刊,UINavigationController 也會受到影響,假如你對View做了Category轰驳,其他繼承View的也會有影響厚掷,而且當(dāng)時交換方法都是在一個Category里管事,到第二個就覆蓋了级解。冒黑。。不造為啥勤哗,因為知道這條路走不通就沒繼續(xù)搞下去了抡爹。。芒划。
寫到這里冬竟,大家應(yīng)該都對我筆下的設(shè)計模式有了一些了解,因為里面涉及的東西確實太多民逼,主要是這些玩意需要站在巨人的肩膀泵殴,遇到文中沒有提到而且不懂得可以: