天天看點

iOS MVVM+RAC 從架構到實戰

一、前言

二、談談MVVM和RAC

1、MVVM淺析

2、RAC淺淺析

3、本篇對兩者的了解運用

二、架構部分

1、架構目錄詳解

2、基類詳解

3、題外話

三、實戰部分(經典清單的實作)

1、LSCircleListViewController的處理

2、View的處理

3、LSCircleListModel的處理

4、ViewModel的處理

5、APPDelegate的代碼簡化

四、後記

一、前言

很早之前就想寫寫自己在設計模式方面的心得,但是一直感覺自己是井底之蛙,畢竟在iOS領域越深入越感到自己的無知,心中有着敬畏之心,就更沒有自信去寫這個東西(你也可以了解是沒時間(>﹏<),請原諒我的裝逼,嘿嘿).
對于設計模式這個讓人又愛又恨的玩意,說來其實簡單,但一千個人眼中就有一千種哈姆雷特,說他千變萬化确實是事實,而且當你深入其中的時候你真的會上瘾,并樂此不疲!
前幾天自己寫的一篇《iOS Xcode全面剖析》閱讀量在短短一天内破千,還上了簡書首頁(你看這句話字型就知道不是廣告了( ⊙o⊙ )),确實很開心,昨天又跟我一朋友用代碼講解了我對MVVM的了解及運用,此情此景下,腦袋一熱搞出一篇來分享給大家也情有可原,當然更希望有更多的大神來指點一下,讓我自己也讓大家有提升就夠了,萬分感謝!

二、談談MVVM和RAC

1、MVVM淺析

到這裡我就預設你看過MVVM相關文章(畢竟相關文章已經可以用滿天飛來形容了~(≧▽≦)/~啦啦啦!),僅僅簡要談談我對其的了解。

MVC是建構iOS App的标準模式,是蘋果推薦的一個用來組織代碼的權威範式,市面上大部分App都是這樣建構的,具體組模組化式不細說,iOS入門者都比較了解(雖然不一定能完全去遵守),但其幾個不能避免的問題卻是很嚴重困擾開發者比如厚重的ViewController、遺失的網絡邏輯(沒有屬于它的位置)、較差的可測試性等是以也就會有維護性較強、耦合性很低的一種新架構MVVM (MVC 引申出得新的架構)的流行。

MVVM雖然來自微軟,但是不應該反對它,它正式規範了正式規範了視圖和控制器緊耦合的性質,如下圖:

MVVM圖示

ViewModel: 相比較于MVC新引入的視圖模型。是視圖顯示邏輯、驗證邏輯、網絡請求等代碼存放的地方,唯一要注意的是,任何視圖本身的引用都不應該放在VM中,換句話說就是VM中不要引入UIKit.h (對于image這個,也有人将其看做資料來處理,這就看個人想法了,并不影響整體的架構)。

這樣,首先解決了VC臃腫的問題,将邏輯代碼、網絡請求等都寫入了VM中,然後又由于VM中包含了所有的展示邏輯而且不會引用V,是以它是可以通過程式設計充分測試的。

so,就是這個樣子的,6666!

2、RAC淺淺析

特别淺。。。本文重點是架構及實戰及MVVM思想,RAC這玩意話說學習曲線較長,難以了解,不好上手,是因為之前學習的時候使用者、中文教程還比較少,是以學習運用起來比較費勁,(當時确實廢了好大得勁,實力裝逼一把 @%&$%& )但現在已經成熟的爛大街了,隻要有心,好的教程一大把,能潛下心來看我寫的水文的人,拿下RAC不在話下!

ReactiveCocoa 可以說是結合了函數式程式設計和響應式程式設計的架構,也可稱其為函數響應式程式設計(FRP)架構,強調一點,RAC雖然最大的優點是提供了一個單一的、統一的方法去處理異步的行為,包括delegate方法,blocks回調,target-action機制,notifications和KVO.但是不要簡單的隻是單純的認為他僅僅就是減少代碼複雜度,更好的配合MVVM而已,小夥子,這樣你就小看它了。

它最大的與衆不同是提供了一種新的寫代碼的思維,由于RAC将Cocoa中KVO、UIKit event、delegate、selector等都增加了RAC支援,是以都不用去做很多跨函數的事。

如果全工程都使用RAC來實作,對于同一個業務邏輯終于可以在同一塊代碼裡完成了,将UI事件,邏輯處理,檔案或資料庫操作,異步網絡請求,UI結果顯示,這一大套統統用函數式程式設計的思路嵌套起來,進入頁面時搭建好這所有的關系,使用者點選後妥妥的等着這一套聯系一個個的按期望的邏輯和次序觸發,最後顯示給使用者。

額,就說這麼多,再說就沒頭了~(≧▽≦)/~啦啦啦!

3、本篇對兩者的了解運用

在此次介紹中,會使用MVVM+RAC結合的方式,搞定一個添加上拉加載及下拉重新整理的清單,是以更多的诠釋MVVM思想,而不是RAC的邏輯鍊式操作(這一點用登入界面來寫更能展現Y^o^Y ),RAC在此扮演的更大一部分的角色是更好的解耦,減少代碼複雜度,使代碼層次分明、邏輯清晰更便于維護更新。

二、架構部分

1、架構目錄詳解

首先介紹一下本架構的目錄結構,如下圖

1、Frameworks

存放系統庫的虛拟檔案夾, 目前搭建架構的時候需要手動添加一個名稱為Frameworks的虛拟檔案夾,這樣你在Build Phases 中添加的系統庫會自動歸入此檔案夾,不會直接在外部顯示以至于打亂目錄結構。系統庫添加流程如下:

另外,細心地家夥會發現此目錄中有兩個相同的Frameworks, 那這到底是什麼鬼?最上面的那個Frameworks是在自己搭架構自己添加的,當時的項目還很單純, 沒有這麼淘氣,問題出在下面那個Pods Target上,添加它之後就會自動給你生成一個虛拟的Frameworks的檔案夾,那又該問了為啥不直接用下面那個呢???(廢話真多!反正也沒沖突,就留着吧╮(╯﹏╰)╭)

既然提到了Pods,那接下來講講CocoaPods(第三方類庫管理工具)。

2、CocoaPods

當你開發iOS應用時,會經常使用到很多第三方開源類庫,比如JSONKit,AFNetWorking等等。可能某個類庫又用到其他類庫,是以要使用它,必須得另外下載下傳其他類庫,而其他類庫又用到其他類庫,“子子孫孫無窮盡也”,反正在早期我是體會過這種痛苦,好心酸,手動一個個去下載下傳所需類庫是十分麻煩的。

還有另外一種常見情況是,你項目中用到的類庫有更新,你必須得重新下載下傳新版本,重新加入到項目中,十分麻煩。

CocoaPods就是幫你解決上面的問題的,話說這玩意應該是iOS最常用最有名的類庫管理工具了,作為iOS程式員的我們,掌握CocoaPods的使用是必不可少的基本技能了,至于這玩意該咋用?

O(∩_∩)O哈哈~你覺得我會告訴你麼?好吧,我這人還是很心軟的,下面一張圖告訴你該咋用...(๑乛◡乛๑ 磨人的小妖精)

☝(•̀˓◞•́)哎呦,不錯哦~是不是get了一個新技能 ?6666!

3、AppDelegate

這個目錄下放的是AppDelegate.h(.m)檔案,是整個應用的入口檔案,是以單獨拿出來。一會兒告訴你如何寫一個簡潔的AppDelegate,會在這個檔案夾裡添加一些類,是以将其放入一個檔案夾内還是很有必要的。

4、Class

工程主體類, 日常大部分開發代碼均在這裡,又細分了好多次級目錄。

通用類

  • General : 通用類(檔案夾項目移植過程中都不需要更改的就能直接使用的)
    • Base : 基類 (整個架構的基類)
    • Categories : 公共擴充類 (就是一些常用的類别,比如分享啊什麼的)
    • Core : 公共核心類(一般存放個人資訊、接口API等)
    • Models : 公共Model (公用的一些資料模型)
    • Views : 公共View (封裝的一些常用的View)

工具類

  • Helpers : 工程的相關輔助類(比如類似資料請求、表單上傳、網絡監測等工具類)

宏定義類

  • Macro : 宏定義類 (就是整個應用會用到的宏定義)
    • AppMacro.h app項目的相關宏定義
    • NotificationMacro.h 通知相關的宏定義
    • VendorMacro.h 第三方相關宏定義
    • UtilsMacro.h 為簡化代碼的宏定義
    • ...等等等等(其他随你定啦!Y^o^Y )

APP具體子產品代碼類

  • Sections : 各子產品的檔案夾(一般而言,我們以人為機關)
    • LSSections 王隆帥的檔案夾
    • CLSections 馬成麟的檔案夾
    • ...等等等等(也可以寫你最喜歡的蒼老師的,叼叼的!)

每個成員的檔案夾下是其所負責子產品的檔案夾,比如蒼老師負責PHP界面子產品(我也認為PHP是最好的語言!大家可以在評論區談論一下!๑乛◡乛๑ 磨人的小妖精),如下(接着上面的個人檔案夾):

  • PHP : 子產品名,也可以是首頁(HomePage)...等等
    • ViewControllers 界面控制器存放處(這是檔案夾名)
    • ViewModels 打雜的(MVVM的核心、解耦合、處理邏輯等)
    • Views 界面相關View存放處不(界面相關子View)
    • Models 資料模型存放處(各種單純的資料模型,一點都不胖,是标準的瘦Model)

這就是标準的MVVM了。。。為啥不和上面目錄連起來呢?為啥呢?為啥呢?因為臣妾做不到啊!!!(不會三級、四級清單的MarkDown寫法,求大神支招!良辰必有重謝!)

第三方類庫

  • Vendors : 第三方的類庫/SDK,如UMeng、WeiboSDK、WeixinSDK等等。
到這哥們又該疑惑了,心裡該碎碎念了:(๑⁼̴̀д⁼̴́๑)ドヤッ‼ What are you 弄啥嘞!剛才剛講了個第三方庫管理CocoaPods,你丫這裡自己又搞了一個,ԅ( ¯་། ¯ԅ) 信不信我突突了你!

哈哈哈,剛才的CocoaPods确實管理着大部分的第三方庫,這裡建立第三方庫目錄的原因有兩個:其一,并不是所有的你需要的第三方都支援pods的,是以還是需要手動添加一些類庫。其二,一些第三方庫雖然支援pods,但是需要我們去更改甚至自定義這個第三方,此時也需要放入這裡,也防止使用pods一不小心更新掉你的自定義!ᕕ(ᐛ)ᕗ 你來打我啊!

5、Resource

這裡放置的是工程所需的一些資源,如下

  • Fonts 字型
  • Images 圖檔(當然你可以添加至Assets.xcassets, 沒人攔着你)
  • Sounds 聲音
  • Videos 視訊

ok,目錄就講到這裡!想知道更詳細的可以私信我!

2、基類詳解

這裡着重講解一下VC、V、VM的基類,其他的模式與View類似是以略過,其中TableViewCell的基類稍微特殊是以也提一下。

我目前的基類如下圖:

是不是眼花缭亂了..., 我曾經也看它不順眼, 曾經嘗試過把基類都幹掉,然後遇到了一些麻煩...就妥協了,在文章的最後可以跟大家聊聊我是怎麼去幹掉基類,然後又失敗的,這裡先詳細講一下基類。

1、YDViewController

函數的具體用意圖已經标的很清楚了,這裡簡單講一下四個函數的作用

  • 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布局

4、YDTableViewCell

由于Cell比較特殊,是以單拎出來說一下。觀察上面的ViewMdoel、View等的基類會發現每個基類都會有資料綁定的地方,但是cell得資料綁定需要放在資料初始化的時候,因為所有的基類的資料邏輯綁定都是在沒有傳回初始化對象的時候調用的,但是cell中假如在那裡面進行資料綁定會出現問題比如下圖:

cell複用失敗

上圖中的函數假如是在 

bindViewModel

 内,則會複用失敗,點選按鈕是沒有反應的,但是假如是在資料初始化的時候調用:比如

setViewModel

 的時候,就會OK了,因為裡面用到了cell的在RAC中複用機制

rac_prepareForReuseSignal

 ,在cell還沒有初始化傳回的時候是失效的。

3、題外話

基類的作用是統一管理,統一風格,便于編碼,有更多的額外的附加功能的話,建議使用Protocol 或 Category,這樣移植性強,便于管理與擴充,不至于牽一發而動全身。

本篇基類核心是用VM來配置V(VC),并提供一些必須的Protocol方法來處理界面顯示、邏輯,将代碼風格規範化,各個部分的功能明朗化,這樣,當你需要寫什麼,需要找什麼,需要更改什麼的時候都會很明确這些代碼的位置,邏輯更清晰,而不會浪費更多的時間在思考應該寫在哪,該去哪找,要改的地方在哪這種不該費時間的問題上。

三、實戰部分(經典清單的實作)

這裡講一下如下界面的代碼構造方式,很普通的一個清單:(懶得再寫了,這是我之前做的一個項目的一個界面,之前基類講解中會看到都是YD開頭的,在這裡是YC開頭就這個差別而已)
首先觀察這個界面,需求是:頭部的内容數量多的話是可以左右滑動的,然後整體是可以上拉加載的。我是這樣處理的:首先界面整體是一個TableView,然後分為一個Header、一個Section和主體清單Row。在Header上嵌套一個CollectionView保證可複用。具體分層如下

然後處理完後的目錄如下:

簡單介紹一下:

  • 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(
           

主View分為四個子產品:

ⅰ 第一個子產品 : 系統函數

每個View都會有對應的ViewModel,這樣也更易複用,這裡因為是主View,一般而言我都會使得VC和主V共用一個VM,這樣對于跳轉、資料共享等都有着極大的好處。

ⅱ 第二個子產品 : 私有函數

具體作用途中已經标注,需要注意的是這些對于不同資料的處理,是我自己寫的,邏輯上肯定沒有那麼缜密,僅供參考。

ⅲ 第三個子產品 : 懶加載

這裡沒啥好說的,就是用的MJRefresh這個第三方庫做的重新整理。不過,假如你細心的話肯定會發現下面那兩個View都是用VM來配置初始化的,這個和主View的配置初始化的意義是一樣的。

ⅳ 第四個子產品 : 代理及資料源

其中使用的是自定義Cell,用ViewModel來配置,點選事件也是和之前的VC的跳轉聯系起來了,并将VM傳過去。

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
           
//
//  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這個資料模型轉換架構。沒有做任何其他的邏輯處理。

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] == 
           

ViewModel也是分為三個子產品,由于代碼太多摘重要的講

ⅰ 第一個子產品 : 處理資料、邏輯子產品

處理資料這塊,先用字典轉為Model,在用Model配置ViewModel,ViewModel再去與UI及其邏輯對應。

ⅱ 第二個子產品 : 私有函數

對于請求參數字典,可以放在VM中,便于子產品化移植,也可以放在公共API中便于管理,看個人選擇了,沒有絕對的好位置,隻有更适合個人的位置。

另外兩個函數就是處理下拉及上拉時有沒有更多資料的私有函數。

ⅲ 第三個子產品 : 懶加載

此資料請求用的是AFNetworking。

5、APPDelegate的代碼簡化

一般而言,我們正式項目中會遇到很多需要啟動項目時就加載的,是以很快APPDelegate就會越來越龐大,既然其他的代碼都簡化解耦了,這裡也可以做一下處理。

目錄如下:

簡化後的AppDelegate如下:

其他代碼存放的位置如下:

當類對象被引入項目時, runtime 會向每一個類對象發送 load 消息. load 方法還是非常的神奇的, 因為它會在每一個類甚至分類被引入時僅調用一次, 調用的順序是父類優先于子類, 子類優先于分類. 而且 load 方法不會被類自動繼承, 每一個類中的 load 方法都不需要像 viewDidLoad 方法一樣調用父類的方法。

這是利用了這個算是黑魔法的玩意,哈哈,就簡化了APPDelegate!

四、後記

當初本來想幹掉基類來着,想利用Category + Protocol并利用Runtime的Methode Swizzle 來給系統函數添加自己的私有函數,當初VC已經搞定了,然而發現這樣牽涉面太廣,你對VC做了Category,UINavigationController 也會受到影響,假如你對View做了Category,其他繼承View的也會有影響,而且當時交換方法都是在一個Category裡管事,到第二個就覆寫了。。。不造為啥,因為知道這條路走不通就沒繼續搞下去了。。。

寫到這裡,大家應該都對我筆下的設計模式有了一些了解,因為裡面涉及的東西确實太多,主要是這些玩意需要站在巨人的肩膀,遇到文中沒有提到而且不懂得可以:

哈哈哈!别怪我...不是我不負責,因為你可以看看寫到這裡篇幅已經超出常人所能接受的了,而且我感覺我把各個細節已經都照顧到了吧(๑乛◡乛๑ 磨人的小妖精)!大家有什麼疑惑我們可以在評論區交流!

最後,真的很希望各位大神指出不足的地方,能讓大家共同進步!

本文由簡書作者 王隆帥 原創編寫,轉載請保留版權網址,感謝您的了解與分享,讓生活變的更美好!(有點吓不住人,該這樣說:如需轉載請務必通知作者,否則法律責任後果自負!)

文/王隆帥(簡書作者)

原文連結:http://www.jianshu.com/p/3beb21d5def2

著作權歸作者所有,轉載請聯系作者獲得授權,并标注“簡書作者”。

iOS