針對最後一小節中幹掉基類的作法(Runtime + category),已經有所實現,並按照本身的思路新寫了個列表實現,具體可查看對於iOS架構模式之爭的一些思考。git
很早以前就想寫寫本身在架構模式方面的心得,可是一直感受本身是井底之蛙,畢竟在iOS領域越深刻越感到本身的無知,心中有着敬畏之心,就更沒有自信去寫這個東西(你也能夠理解是沒時間(>﹏<),請原諒個人裝逼,嘿嘿).程序員
對於架構模式這個讓人又愛又恨的玩意,說來其實簡單,但一千我的眼中就有一千種哈姆雷特,說他變幻無窮確實是事實,並且當你深刻其中的時候你真的會上癮,並樂此不疲!github
前幾天本身寫的一篇《iOS Xcode全面剖析》閱讀量在短短一天內破千,還上了簡書首頁(你看這句話字體就知道不是廣告了( ⊙o⊙ )),確實很開心,昨天又跟我一朋友用代碼講解了我對MVVM的理解及運用,此情此景下,腦殼一熱搞出一篇來分享給你們也情有可原,固然更但願有更多的大神來指點一下,讓我本身也讓你們有提高就夠了,萬分感謝!數據庫
到這裏我就默認你看過MVVM相關文章(畢竟相關文章已經能夠用滿天飛來形容了~(≧▽≦)/~啦啦啦!),僅僅簡要談談我對其的理解。編程
MVC是構建iOS App的標準模式,是蘋果推薦的一個用來組織代碼的權威範式,市面上大部分App都是這樣構建的,具體組建模式不細說,iOS入門者都比較瞭解(雖然不必定能徹底去遵照),但其幾個不能避免的問題倒是很嚴重困擾開發者好比厚重的ViewController、遺失的網絡邏輯(沒有屬於它的位置)、較差的可測試性等所以也就會有維護性較強、耦合性很低的一種新架構MVVM (MVC 引伸出得新的架構)的流行。網絡
MVVM雖然來自微軟,可是不該該反對它,它正式規範了正式規範了視圖和控制器緊耦合的性質,以下圖:數據結構
ViewModel: 相比較於MVC新引入的視圖模型。是視圖顯示邏輯、驗證邏輯、網絡請求等代碼存放的地方,惟一要注意的是,任何視圖自己的引用都不該該放在VM中,換句話說就是VM中不要引入UIKit.h (對於image這個,也有人將其看作數據來處理,這就看我的想法了,並不影響總體的架構)。架構
這樣,首先解決了VC臃腫的問題,將邏輯代碼、網絡請求等都寫入了VM中,而後又因爲VM中包含了全部的展現邏輯並且不會引用V,因此它是能夠經過編程充分測試的。app
so,就是這個樣子的,6666!框架
特別淺。。。本文重點是框架及實戰及MVVM思想,RAC這玩意話說學習曲線較長,難以理解,很差上手,是由於以前學習的時候使用者、中文教程還比較少,因此學習運用起來比較費勁,(當時確實廢了好大得勁,實力裝逼一把 @%&$%& )但如今已經成熟的爛大街了,只要有心,好的教程一大把,能潛下心來看我寫的水文的人,拿下RAC不在話下!
ReactiveCocoa 能夠說是結合了函數式編程和響應式編程的框架,也可稱其爲函數響應式編程(FRP)框架,強調一點,RAC雖然最大的優勢是提供了一個單一的、統一的方法去處理異步的行爲,包括delegate方法,blocks回調,target-action機制,notifications和KVO.可是不要簡單的只是單純的認爲他僅僅就是減小代碼複雜度,更好的配合MVVM而已,小夥子,這樣你就小看它了。
它最大的不同凡響是提供了一種新的寫代碼的思惟,因爲RAC將Cocoa中KVO、UIKit event、delegate、selector等都增長了RAC支持,因此都不用去作不少跨函數的事。
若是全工程都使用RAC來實現,對於同一個業務邏輯終於能夠在同一塊代碼裏完成了,將UI事件,邏輯處理,文件或數據庫操做,異步網絡請求,UI結果顯示,這一大套通通用函數式編程的思路嵌套起來,進入頁面時搭建好這全部的關係,用戶點擊後妥妥的等着這一套聯繫一個個的定期望的邏輯和次序觸發,最後顯示給用戶。
額,就說這麼多,再說就沒頭了~(≧▽≦)/~啦啦啦!
在這次介紹中,會使用MVVM+RAC結合的方式,搞定一個添加上拉加載及下拉刷新的列表,因此更多的詮釋MVVM思想,而不是RAC的邏輯鏈式操做(這一點用登陸界面來寫更能體現Y^o^Y ),RAC在此扮演的更大一部分的角色是更好的解耦,減小代碼複雜度,使代碼井井有條、邏輯清晰更便於維護升級。
首先介紹一下本框架的目錄結構,以下圖
一、Frameworks
存放系統庫的虛擬文件夾, 目前搭建框架的時候須要手動添加一個名稱爲Frameworks的虛擬文件夾,這樣你在Build Phases 中添加的系統庫會自動納入此文件夾,不會直接在外部顯示以致於打亂目錄結構。系統庫添加流程以下:
另外,細心地傢伙會發現此目錄中有兩個相同的Frameworks, 那這究竟是什麼鬼?最上面的那個Frameworks是在本身搭框架本身添加的,當時的項目還很單純, 沒有這麼淘氣,問題出在下面那個Pods Target上,添加它以後就會自動給你生成一個虛擬的Frameworks的文件夾,那又該問了爲啥不直接用下面那個呢???(廢話真多!反正也沒衝突,就留着吧╮(╯﹏╰)╭)
既然提到了Pods,那接下來說講CocoaPods(第三方類庫管理工具)。
二、CocoaPods
當你開發iOS應用時,會常用到不少第三方開源類庫,好比JSONKit,AFNetWorking等等。可能某個類庫又用到其餘類庫,因此要使用它,必須得另外下載其餘類庫,而其餘類庫又用到其餘類庫,「子子孫孫無窮盡也」,反正在早期我是體會過這種痛苦,好心酸,手動一個個去下載所需類庫是十分麻煩的。
還有另一種常見狀況是,你項目中用到的類庫有更新,你必須得從新下載新版本,從新加入到項目中,十分麻煩。
CocoaPods就是幫你解決上面的問題的,話說這玩意應該是iOS最經常使用最有名的類庫管理工具了,做爲iOS程序員的咱們,掌握CocoaPods的使用是必不可少的基本技能了,至於這玩意該咋用?
O(∩_∩)O哈哈~你以爲我會告訴你麼?好吧,我這人仍是很心軟的,下面一張圖告訴你該咋用...(๑乛◡乛๑ 磨人的小妖精)
☝(•̀˓◞•́)哎呦,不錯哦~是否是get了一個新技能 ?6666!
三、AppDelegate
這個目錄下放的是AppDelegate.h(.m)文件,是整個應用的入口文件,因此單獨拿出來。一下子告訴你如何寫一個簡潔的AppDelegate,會在這個文件夾裏添加一些類,因此將其放入一個文件夾內仍是頗有必要的。
四、Class
工程主體類, 平常大部分開發代碼均在這裏,又細分了好屢次級目錄。
通用類
工具類
宏定義類
APP具體模塊代碼類
每一個成員的文件夾下是其所負責模塊的文件夾,好比蒼老師負責PHP界面模塊(我也認爲PHP是最好的語言!你們能夠在評論區談論一下!๑乛◡乛๑ 磨人的小妖精),以下(接着上面的我的文件夾):
這就是標準的MVVM了。。。爲啥不和上面目錄連起來呢?爲啥呢?爲啥呢?由於臣妾作不到啊!!!(不會三級、四級列表的MarkDown寫法,求大神支招!良辰必有重謝!)
第三方類庫
到這哥們又該疑惑了,內心該碎碎唸了:(๑⁼̴̀д⁼̴́๑)ドヤッ‼ What are you 弄啥嘞!剛纔剛講了個第三方庫管理CocoaPods,你丫這裏本身又搞了一個,ԅ( ¯་། ¯ԅ) 信不信我突突了你!
哈哈哈,剛纔的CocoaPods確實管理着大部分的第三方庫,這裏創建第三方庫目錄的緣由有兩個:其一,並非全部的你須要的第三方都支持pods的,因此仍是須要手動添加一些類庫。其二,一些第三方庫雖然支持pods,可是須要咱們去更改甚至自定義這個第三方,此時也須要放入這裏,也防止使用pods一不當心更新掉你的自定義!ᕕ(ᐛ)ᕗ 你來打我啊!
五、Resource
這裏放置的是工程所需的一些資源,以下
ok,目錄就講到這裏!想知道更詳細的能夠私信我!
這裏着重講解一下VC、V、VM的基類,其餘的模式與View相似因此略過,其中TableViewCell的基類稍微特殊因此也提一下。
我目前的基類以下圖:
是否是眼花繚亂了..., 我曾經也看它不順眼, 曾經嘗試過把基類都幹掉,而後遇到了一些麻煩...就妥協了,在文章的最後能夠跟你們聊聊我是怎麼去幹掉基類,而後又失敗的,這裏先詳細講一下基類。
一、YDViewController
函數的具體用意圖已經標的很清楚了,這裏簡單講一下四個函數的做用
yd_addSubviews : 添加View到ViewController
yd_bindViewModel : 用來綁定V(VC)與VM
yd_layoutNavigation : 設置導航欄、分欄
yd_getNewData : 初次獲取數據的時候調用(不是特別必要)
二、YDView
三、YDViewModel
四、YDTableViewCell
因爲Cell比較特殊,因此單拎出來講一下。觀察上面的ViewMdoel、View等的基類會發現每一個基類都會有數據綁定的地方,可是cell得數據綁定須要放在數據初始化的時候,由於全部的基類的數據邏輯綁定都是在沒有返回初始化對象的時候調用的,可是cell中假如在那裏面進行數據綁定會出現問題好比下圖:
上圖中的函數假如是在 bindViewModel
內,則會複用失敗,點擊按鈕是沒有反應的,可是假如是在數據初始化的時候調用:好比 setViewModel
的時候,就會OK了,由於裏面用到了cell的在RAC中複用機制 rac_prepareForReuseSignal
,在cell尚未初始化返回的時候是失效的。
基類的做用是統一管理,統一風格,便於編碼,有更多的額外的附加功能的話,建議使用Protocol 或 Category,這樣移植性強,便於管理與擴展,不至於牽一髮而動全身。
本篇基類核心是用VM來配置V(VC),並提供一些必須的Protocol方法來處理界面顯示、邏輯,將代碼風格規範化,各個部分的功能明朗化,這樣,當你須要寫什麼,須要找什麼,須要更改什麼的時候都會很明確這些代碼的位置,邏輯更清晰,而不會浪費更多的時間在思考應該寫在哪,該去哪找,要改的地方在哪這種不應費時間的問題上。
這裏講一下以下界面的代碼構造方式,很普通的一個列表:(懶得再寫了,這是我以前作的一個項目的一個界面,以前基類講解中會看到都是YD開頭的,在這裏是YC開頭就這個區別而已)
首先觀察這個界面,需求是:頭部的內容數量多的話是能夠左右滑動的,而後總體是能夠上拉加載的。我是這樣處理的:首先界面總體是一個TableView,而後分爲一個Header、一個Section和主體列表Row。在Header上嵌套一個CollectionView保證可複用。具體分層以下
而後處理完後的目錄以下:
簡單介紹一下:
一個小小的界面這麼多類...是否是難以接受了,淡定些,騷年!你要想一想把這些個東西都放在VC內是個什麼趕腳?也得好幾千行呢!(有點誇張!不過也夠頭疼的),這麼多類,這裏着重講一下主VC、主V、主VM、主M就ok,能詳細講明白MVVM之間是如何工做的就一通百通了。
先上代碼:
// // 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不隱藏
ⅲ 第三個模塊 : 懶加載
這就不用解釋了,用到時再加載。
先上代碼
// // 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
主View分爲四個模塊:
ⅰ 第一個模塊 : 系統函數
每一個View都會有對應的ViewModel,這樣也更易複用,這裏由於是主View,通常而言我都會使得VC和主V共用一個VM,這樣對於跳轉、數據共享等都有着極大的好處。
ⅱ 第二個模塊 : 私有函數
具體做用途中已經標註,須要注意的是這些對於不一樣數據的處理,是我本身寫的,邏輯上確定沒有那麼縝密,僅供參考。
ⅲ 第三個模塊 : 懶加載
這裏沒啥好說的,就是用的MJRefresh這個第三方庫作的刷新。不過,假如你細心的話確定會發現下面那兩個View都是用VM來配置初始化的,這個和主View的配置初始化的意義是同樣的。
ⅳ 第四個模塊 : 代理及數據源
其中使用的是自定義Cell,用ViewModel來配置,點擊事件也是和以前的VC的跳轉聯繫起來了,並將VM傳過去。
一樣,先上代碼
// // 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
// // LSCircleListModel.m // ZhongShui // // Created by 王隆帥 on 16/3/17. // Copyright © 2016年 王隆帥. All rights reserved. // #import "LSCircleListModel.h" @implementation LSCircleListModel + (NSDictionary *)mj_replacedKeyFromPropertyName { return @{ @"idStr":@"id", @"title":@"title", @"intro":@"intro", @"img":@"img", @"memberCount":@"MemberCount", @"topicCount":@"TopicCount", }; } @end
這個就不貼圖介紹了,就是單純的數據模型,使用了MJExtention這個數據模型轉換框架。沒有作任何其餘的邏輯處理。
// // 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中便於管理,看我的選擇了,沒有絕對的好位置,只有更適合我的的位置。
另外兩個函數就是處理下拉及上拉時有沒有更多數據的私有函數。
ⅲ 第三個模塊 : 懶加載
此數據請求用的是AFNetworking。
通常而言,咱們正式項目中會遇到不少須要啓動項目時就加載的,因此很快APPDelegate就會愈來愈龐大,既然其餘的代碼都簡化解耦了,這裏也能夠作一下處理。
目錄以下:
簡化後的AppDelegate以下:
其餘代碼存放的位置以下:
當類對象被引入項目時, runtime 會向每個類對象發送 load 消息. load 方法仍是很是的神奇的, 由於它會在每個類甚至分類被引入時僅調用一次, 調用的順序是父類優先於子類, 子類優先於分類. 並且 load 方法不會被類自動繼承, 每個類中的 load 方法都不須要像 viewDidLoad 方法同樣調用父類的方法。
這是利用了這個算是黑魔法的玩意,哈哈,就簡化了APPDelegate!
當初原本想幹掉基類來着,想利用Category + Protocol並利用Runtime的Methode Swizzle 來給系統函數添加本身的私有函數,當初VC已經搞定了,然而發現這樣牽涉面太廣,你對VC作了Category,UINavigationController 也會受到影響,假如你對View作了Category,其餘繼承View的也會有影響,並且當時交換方法都是在一個Category裏管事,到第二個就覆蓋了。。。不造爲啥,由於知道這條路走不通就沒繼續搞下去了。。。
07.04 更新
針對本小節中幹掉基類的作法(Runtime + category),已經有所實現,並按照本身的思路新寫了個列表實現,具體可查看對於iOS架構模式之爭的一些思考。
寫到這裏,你們應該都對我筆下的架構模式有了一些瞭解,由於裏面涉及的東西確實太多,主要是這些玩意須要站在巨人的肩膀,遇到文中沒有提到並且不懂得能夠:
哈哈哈!別怪我...不是我不負責,由於你能夠看看寫到這裏篇幅已經超出常人所能接受的了,並且我感受我把各個細節已經都照顧到了吧(๑乛◡乛๑ 磨人的小妖精)!你們有什麼疑惑咱們能夠在評論區交流!
最後,真的很但願各位大神指出不足的地方,能讓你們共同進步!
代碼地址:https://github.com/wanglongshuai/MVVM-RAC-Demo
本文由簡書做者 王隆帥 原創編寫,轉載請保留版權網址,感謝您的理解與分享,讓生活變的更美好!(有點嚇不住人,該這樣說:如需轉載請務必通知做者,不然法律責任後果自負!)