在說(shuō)MVVM之前,首先要了解我們最常用的MVC設(shè)計(jì)模式??
1.MVC設(shè)計(jì)模式
蘋果官方將MVC設(shè)計(jì)模式作為iOS APP的標(biāo)準(zhǔn)模式
斯坦福大學(xué)公開課上的這幅圖來(lái)說(shuō)明,這可以說(shuō)是最經(jīng)典和最規(guī)范的MVC標(biāo)準(zhǔn)
MVC是最普遍認(rèn)知的設(shè)計(jì)模式(高內(nèi)聚,低耦合),MVC模式將頁(yè)面的邏輯分為3塊:Model(模型數(shù)據(jù)業(yè)務(wù))、View(UI展示業(yè)務(wù))、Controller(協(xié)調(diào)者-控制器)
Model(模型):是應(yīng)用程序中用于處理應(yīng)用程序數(shù)據(jù)邏輯的部分。
通常模型對(duì)象負(fù)責(zé)在數(shù)據(jù)庫(kù)中存取數(shù)據(jù)。
比如我們?nèi)祟愑幸浑p手,一雙眼睛,一個(gè)腦袋,沒(méi)有尾巴,這就是模型,Model定義了這個(gè)模塊的數(shù)據(jù)模型。
在代碼中體現(xiàn)為數(shù)據(jù)管理者,Model負(fù)責(zé)對(duì)數(shù)據(jù)進(jìn)行獲取及存放。
數(shù)據(jù)不可能憑空生成的,要么是從服務(wù)器上面獲取到的數(shù)據(jù),要么是本地?cái)?shù)據(jù)庫(kù)中的數(shù)據(jù),
也有可能是用戶在UI上填寫的表單即將上傳到服務(wù)器上面存放,所以需要有數(shù)據(jù)來(lái)源。
既然Model是數(shù)據(jù)管理者,則自然由它來(lái)負(fù)責(zé)獲取數(shù)據(jù)。
Controller不需要關(guān)心Model是如何拿到數(shù)據(jù)的,只管調(diào)用就行了。
數(shù)據(jù)存放的地方是在Model,而使用數(shù)據(jù)的地方是在Controller,
所以Model應(yīng)該提供接口供controller訪問(wèn)其存放的數(shù)據(jù)(通常通過(guò).h里面的只讀屬性)
View(視圖):是應(yīng)用程序中處理數(shù)據(jù)顯示的部分。
通常視圖是依據(jù)模型數(shù)據(jù)創(chuàng)建的。
View,視圖,簡(jiǎn)單來(lái)說(shuō),就是我們?cè)诮缑嫔峡匆姷囊磺小?它們有一部分是我們UI定死的,也就是不會(huì)根據(jù)數(shù)據(jù)來(lái)更新顯示的,
比如一些Logo圖片啊,這里有個(gè)按鈕啊,那里有個(gè)輸入框啊,一些顯示特定內(nèi)容的label啊等等;
有一部分是會(huì)根據(jù)數(shù)據(jù)來(lái)顯示內(nèi)容的,比如tableView來(lái)顯示好友列表啊,
這個(gè)tableView的顯示內(nèi)容肯定是根據(jù)數(shù)據(jù)來(lái)顯示的。
我們使用MVC解決問(wèn)題的時(shí)候,通常是解決這些根據(jù)數(shù)據(jù)來(lái)顯示內(nèi)容的視圖。
Controller(控制器):是應(yīng)用程序中處理用戶交互的部分。
通常控制器負(fù)責(zé)從視圖讀取數(shù)據(jù),控制用戶輸入,并向模型發(fā)送數(shù)據(jù)。
Controller是MVC中的數(shù)據(jù)和視圖的協(xié)調(diào)者,也就是在Controller里面把Model的數(shù)據(jù)賦值給View來(lái)顯示
(或者是View接收用戶輸入的數(shù)據(jù)然后由Controller把這些數(shù)據(jù)傳給Model來(lái)保存到本地或者上傳到
服務(wù)器)。
controller
出現(xiàn)的原因:我們所有的App都是界面和數(shù)據(jù)的交互,所以需要類來(lái)進(jìn)行界面的繪制,于是出現(xiàn)了View
,需要類來(lái)管理數(shù)據(jù)于是出現(xiàn)了Model
。我們?cè)O(shè)計(jì)的View
應(yīng)該能顯示任意的內(nèi)容比如頁(yè)面中顯示的文字應(yīng)該是任意的而不只是某個(gè)特定Model
的內(nèi)容,所以我們不應(yīng)該在View
的實(shí)現(xiàn)中去寫和Model
相關(guān)的任何代碼,如果這樣做了,那么View
的可擴(kuò)展性就相當(dāng)?shù)土恕6?code>Model只是負(fù)責(zé)處理數(shù)據(jù)的,它根本不知道數(shù)據(jù)到時(shí)候會(huì)拿去干啥,可能拿去作為算法噼里啪啦去了,可能拿去顯示給用戶了,它既然無(wú)法接收用戶的交互,它就不應(yīng)該去管和視圖相關(guān)的任何信息,所以Model
中不應(yīng)該寫任何View
相關(guān)代碼。然而我們的數(shù)據(jù)和界面應(yīng)該同步,也就是一定要有個(gè)地方要把Model
的數(shù)據(jù)賦值給View
,而Model
內(nèi)部和View
的內(nèi)部都不可能去寫這樣的代碼,所以只能新創(chuàng)造一個(gè)類出來(lái)了,取名為Controller
。
2.MVC缺點(diǎn)
上述對(duì)于MVC的描述是理想狀態(tài)下的MVC,
Controller
的作用應(yīng)該是一個(gè)橋梁,建立起Model
和View
的連接,但實(shí)際開發(fā)中總是會(huì)出現(xiàn)Controller厚重
,重量級(jí)控制器的問(wèn)題,原因如下
- 繁重的UI
- 啰嗦的業(yè)務(wù)邏輯
- 各種代理
- 遺失的網(wǎng)絡(luò)邏輯(無(wú)立足之地)
- 較差的可測(cè)試性
在iOS開發(fā)中,UIKIt框架是將控制器Controller與View進(jìn)行綁定了的,每個(gè)控制器都有View對(duì)象,代碼添加UI子控件細(xì)節(jié)或者在xib與storyboard中子視圖可以直接與controller進(jìn)行關(guān)聯(lián),都會(huì)導(dǎo)致控制器中難以避免很多本該View去負(fù)責(zé)的UI子控件細(xì)節(jié)處理放在了控制器Controller里面;而在Controller里面本身要處理的請(qǐng)求、控制器生命周期函數(shù)要處理的事情比較多的情況下,控制器就變得很臃腫。實(shí)際上這個(gè)設(shè)計(jì)模式在iOS中為:M-VC
因此,M-VC
可能是對(duì) iOS 開發(fā)中的 MVC模式更為準(zhǔn)確的解讀,同時(shí)更也準(zhǔn)確地描述了我們?nèi)粘i_發(fā)可能已經(jīng)編寫的 MVC 代碼,但它并沒(méi)有做太多事情來(lái)解決 iOS 應(yīng)用中日益增長(zhǎng)的重量級(jí)視圖控制器的問(wèn)題。
舉個(gè)例子: cell傳值,就需要在VC里解析完數(shù)據(jù)在將model傳給cell的view,增加了model和view之間的耦合,就變成了
M-VC
這里還要特殊說(shuō)一下那無(wú)處安放網(wǎng)絡(luò)請(qǐng)求
蘋果使用的MVC的定義是這么說(shuō)的:所有的對(duì)象都可以被歸類為一個(gè)model,一個(gè)view,或是一個(gè)controller。就這些。那么把網(wǎng)絡(luò)代碼放哪里?和一個(gè)API通信的代碼應(yīng)該放在哪兒?
你可能試著把它放在model對(duì)象里,但是也會(huì)很棘手,因?yàn)榫W(wǎng)絡(luò)調(diào)用應(yīng)該使用異步,這樣如果一個(gè)網(wǎng)絡(luò)請(qǐng)求比持有它的model生命周期更長(zhǎng),事情將變的復(fù)雜。顯然也不應(yīng)該把網(wǎng)絡(luò)代碼放在view里,因此只剩下controller了。這同樣是個(gè)壞主意,因?yàn)檫@加劇了厚重View Controller的問(wèn)題。
那么應(yīng)該放在那里呢?顯然MVC的3大組件根本沒(méi)有適合放這些代碼的地方。
3.MVVM設(shè)計(jì)模式
MVVM的誕生
就像我們之前分析MVC是如何合理分配工作的一樣,我們需要數(shù)據(jù)所以有了M,我們需要界面所以有了V,而我們需要找一個(gè)地方把M賦值給V來(lái)顯示,所以有了C,然而我們忽略了一個(gè)很重要的操作:數(shù)據(jù)解析。在MVC出生的年代,手機(jī)APP的數(shù)據(jù)往往都比較簡(jiǎn)單,沒(méi)有現(xiàn)在那么復(fù)雜,所以那時(shí)的數(shù)據(jù)解析很可能一步就解決了,所以既然有這樣一個(gè)問(wèn)題要處理,而面向?qū)ο蟮乃枷刖褪怯妙惡蛯?duì)象來(lái)解決問(wèn)題,顯然V和M早就被定義死了,它們都不應(yīng)該處理“解析數(shù)據(jù)”的問(wèn)題,理所應(yīng)當(dāng)?shù)模敖馕鰯?shù)據(jù)”這個(gè)問(wèn)題就交給C來(lái)完成了。而現(xiàn)在的手機(jī)App功能越來(lái)越復(fù)雜,數(shù)據(jù)結(jié)構(gòu)也越來(lái)越復(fù)雜,所以數(shù)據(jù)解析也就沒(méi)那么簡(jiǎn)單了。如果我們繼續(xù)按照MVC的設(shè)計(jì)思路,將數(shù)據(jù)解析的部分放到了Controller里面,那么Controller就將變得相當(dāng)臃腫。還有相當(dāng)重要的一點(diǎn):Controller被設(shè)計(jì)出來(lái)并不是處理數(shù)據(jù)解析的。1、管理自己的生命周期;2、處理Controller之間的跳轉(zhuǎn);3、實(shí)現(xiàn)Controller容器。這里面根本沒(méi)有“數(shù)據(jù)解析”這一項(xiàng),所以顯然,數(shù)據(jù)解析也不應(yīng)該由Controller來(lái)完成。那么我們的MVC中,M、V、C都不應(yīng)該處理數(shù)據(jù)解析,那么由誰(shuí)來(lái)呢?這個(gè)問(wèn)題實(shí)際上在面向?qū)ο蟮臅r(shí)候相當(dāng)好回答:既然目前沒(méi)有類能夠處理這個(gè)問(wèn)題,那么就創(chuàng)建一個(gè)新的類出來(lái)解決不就好了?所以我們聰明的開發(fā)者們就專門為數(shù)據(jù)解析創(chuàng)建出了一個(gè)新的類:ViewModel。這就是MVVM的誕生。
MVVVM解決的問(wèn)題,你只需要記住兩點(diǎn):1、Controller的存在感被完全的降低了;2、VM的出現(xiàn)就是Controller存在感降低的原因。
Controller存在感降低的原因
在MVVM中,Controller不再像MVC那樣直接持有Model了。想象Controller是一個(gè)Boss,數(shù)據(jù)是一堆文件(Model),如果現(xiàn)在是MVC,那么數(shù)據(jù)解析(比如整理文件)需要由Boss親自完成,然而實(shí)際上Boss需要的僅僅是整理好的文件而不是那一堆亂七八糟的整理前的文件。所以Boss招聘了一個(gè)秘書,現(xiàn)在Boss就不再需要管理原始數(shù)據(jù)(整理之前的文件)了,他只需要去找秘書:你幫我把文件整理好后給我。那么這個(gè)秘書就首先去拿到文件(原始數(shù)據(jù)),然后進(jìn)行整理(數(shù)據(jù)解析),接下來(lái)把整理的結(jié)果給Boss。所以秘書就是VM了,并且Controller(Boss)現(xiàn)在只需要直接持有VM而不需要再持有M了。如果再進(jìn)一步理解C、VM、M之間的關(guān)系:因?yàn)镃ontroller只需要數(shù)據(jù)解析的結(jié)果而不關(guān)心過(guò)程,所以就相當(dāng)于VM把“如何解析Model”給封裝起來(lái)了,C甚至根本就不需要知道M的存在就能把工作做好,前提它需要持有一個(gè)VM。那么我們MVVM中的持有關(guān)系就是:C持有VM,VM持有M。這里有一個(gè)比較爭(zhēng)議的地方:C該不該持有M。我的答案是不該。為什么呢,因?yàn)镃持有M沒(méi)有任何意義。就算C直接拿到了M的數(shù)據(jù),它還是要去讓VM進(jìn)行數(shù)據(jù)解析,而數(shù)據(jù)解析就需要M,那么直接讓VM持有M而C直接持有VM就足夠了。最后再分享一個(gè)我在實(shí)現(xiàn)MVVM中的一個(gè)技巧,也談不上是技巧吧,算是一種必要的思想:一旦在實(shí)現(xiàn)Controller的過(guò)程中遇到任何跟Model(或者數(shù)據(jù))相關(guān)的問(wèn)題,就找VM要答案。這個(gè)思想待會(huì)我們會(huì)在實(shí)現(xiàn)代碼的時(shí)候用到。
MVVM雙向綁定的實(shí)現(xiàn)
使用block和監(jiān)聽實(shí)現(xiàn)該功能
ViewController核心代碼
@property (nonatomic, strong) NSMutableArray *dataArray;
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) MVVMViewModel *vm;
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.tableView];
self.tableView.dataSource = self;
self.tableView.delegate = self;
self.vm = [[MVVMViewModel alloc] init];
__weak typeof(self) weakSelf = self;
//model -> UI -> 代碼塊
[self.vm initWithBlock:^(id data) {
//獲取到更新后的數(shù)據(jù),刷新UI
NSArray *array = data;
[weakSelf.dataArray removeAllObjects];
[weakSelf.dataArray addObjectsFromArray:array];
MVVMView *view = [[MVVMView alloc]initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, (array.count + 1)/4*50)];
[view headViewWithData:array];
weakSelf.tableView.tableHeaderView = view;
[weakSelf.tableView reloadData];
} WithErrorBlock:^(id errorCode) {
}];
[self.vm reloadData];
}
#pragma mark - tableViewDelegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
self.vm.contenKey = self.dataArray[indexPath.row];
//這里通過(guò)修改MVVMViewModel的contenKey來(lái)更新數(shù)據(jù)(觸發(fā)通知方法),更新好的數(shù)據(jù)在通過(guò)block回調(diào)到VC中,完成了MVVM的雙向綁定
}
ViewModel核心代碼
//.h代碼
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "ViewModelClass.h" //父類為封裝好方便調(diào)用block的基類,具體可參照demo
@interface MVVMViewModel : ViewModelClass
@property (nonatomic, copy) NSString *contenKey; //通過(guò)KVO監(jiān)聽該值,當(dāng)該值發(fā)生改變后更新數(shù)據(jù),回調(diào)VC
- (void)reloadData;
@end
//.m代碼
#import "MVVMViewModel.h"
@implementation MVVMViewModel
- (instancetype)init{
if (self == [super init]) {
[self addObserver:self forKeyPath:@"contenKey" options:NSKeyValueObservingOptionNew context:nil];
}
return self;
}
- (void)reloadData{
//數(shù)據(jù)一般為網(wǎng)絡(luò)獲取,獲取后進(jìn)行回調(diào),VC刷新頁(yè)面
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSArray *array = @[@"轉(zhuǎn)賬",@"信用卡",@"充值中心",@"螞蟻借唄",@"電影票",@"滴滴出行",@"城市服務(wù)",@"螞蟻森林"];
dispatch_async(dispatch_get_main_queue(), ^{
if (self.successBlock) {
//返回block
self.successBlock(array);
}
});
});
}
#pragma mark - KVO回調(diào)
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"%@",change); //通過(guò)監(jiān)聽得知數(shù)據(jù)變化
//處理數(shù)據(jù)
NSArray *array = @[@"轉(zhuǎn)賬",@"信用卡",@"充值中心",@"螞蟻借唄",@"電影票",@"滴滴出行",@"城市服務(wù)",@"螞蟻森林"];
NSMutableArray *mArray = [NSMutableArray arrayWithArray:array];
[mArray removeObject:change[NSKeyValueChangeNewKey]];
//雙向綁定通知VC更新變化后的數(shù)據(jù)
if (self.successBlock) {
self.successBlock(mArray);
}
}
- (void)dealloc
{
[self removeObserver:self forKeyPath:@"contenKey"]; //移除通知
}
@end
實(shí)際開發(fā)中,我們可以搭配RAC使用MVVM的架構(gòu)模式,代碼更簡(jiǎn)潔、易懂
4.ReactiveCocoa簡(jiǎn)介
ReactiveCocoa是響應(yīng)式編程(FRP)在iOS和OS中的一個(gè)實(shí)現(xiàn)框架,它的開源地址為:https://github.com/ReactiveCocoa/ReactiveCocoa
優(yōu)點(diǎn)
RAC雖然最大的優(yōu)點(diǎn)是提供了一個(gè)單一的、統(tǒng)一的方法去處理異步的行為,包括delegate
方法、blocks
回調(diào)、target-action
機(jī)制、notifications
和KVO
。
詳細(xì)來(lái)說(shuō),在iOS開發(fā)過(guò)程中,當(dāng)某些事件響應(yīng)的時(shí)候,需要處理某些業(yè)務(wù)邏輯,這些事件都用不同的方式來(lái)處理。
比如按鈕的點(diǎn)擊使用action,ScrollView滾動(dòng)使用delegate,屬性值改變使用KVO等系統(tǒng)提供的方式。
其實(shí)這些事件,都可以通過(guò)RAC處理
ReactiveCocoa為事件提供了很多處理方法,而且利用RAC處理事件很方便,可以把要處理的事情,和監(jiān)聽的事情的代碼放在一起,這樣非常方便我們管理,就不需要跳到對(duì)應(yīng)的方法里。非常符合我們開發(fā)中高聚合,低耦合的思想。
集成RAC
通過(guò)pods集成,集成步驟可以參考這兩篇文章
ios 通過(guò)CocoaPods安裝第三方庫(kù)
Xcode如何集成Pod教程
ps:該三方庫(kù)支持多個(gè)平臺(tái)iOS、OS、Swift,iOS集成pod 'ReactiveObjC', '~> 3.1.0'
RAC常用語(yǔ)法
- UITextField
@weakify(self);
[[self.testTextFileld rac_textSignal] subscribeNext:^(NSString * _Nullable x) {
@strongify(self);
NSLog(@"%@",x);
self.testTextFileld.text = @"Hello";
}];
監(jiān)聽了輸入框內(nèi)所有的變化,包括準(zhǔn)備編輯,和退出編輯。再也不用寫delegate了,編碼起來(lái)方便快捷!!!
- UIButton
[[self.btn rac_signalForControlEvents:(UIControlEventTouchUpInside)] subscribeNext:^(__kindof UIControl * _Nullable x) {
NSLog(@"%@",[x class]);
}];
平常寫按鈕的觸發(fā)事件都要新建一個(gè)方法去實(shí)現(xiàn),現(xiàn)在不用了,直接在你的按鈕下面寫實(shí)現(xiàn)的代碼。實(shí)例化和觸發(fā)事件寫在一起,查閱代碼和維護(hù)代碼更加直觀!!!
- NSNotificationCenter
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIApplicationDidEnterBackgroundNotification object:nil] subscribeNext:^(NSNotification * _Nullable x) {
NSLog(@"%@",x);
}];
還能監(jiān)聽通知的各種事件,上面就是監(jiān)聽了APP退到后臺(tái)的事件。最重要的一點(diǎn)就是不需要移除通知,比通知用起來(lái)更爽,無(wú)后顧之憂!!!
MVVM+RAC代碼
只需要把上述代碼的init方法修改為RAC代碼即可
- (instancetype)init{
if (self == [super init]) {
//訂閱信號(hào)(熱信號(hào)!!) 監(jiān)聽contenKey值的變化
[RACObserve(self, contenKey) subscribeNext:^(id _Nullable x) {
NSArray *array = @[@"轉(zhuǎn)賬",@"信用卡",@"充值中心",@"螞蟻借唄",@"電影票",@"滴滴出行",@"城市服務(wù)",@"螞蟻森林"];
NSMutableArray *mArray = [NSMutableArray arrayWithArray:array];
[mArray removeObject:x];
if (self.successBlock) {
self.successBlock(mArray);
}
}];
}
return self;
}
上述代碼已經(jīng)實(shí)現(xiàn)了MVVM+RAC的開發(fā),那么我們項(xiàng)目中的MVC架構(gòu)模式應(yīng)該如何去優(yōu)化呢?
5.如何對(duì) ViewController 瘦身?
- 將網(wǎng)絡(luò)請(qǐng)求抽象到單獨(dú)的類中
- 將界面的拼裝抽象到專門的類中
- 創(chuàng)建構(gòu)造類似
ViewModel
的工廠類,參見 工廠模式。另外,也可以專門將數(shù)據(jù)存取都抽將到一個(gè)Service
層,由這層來(lái)提供ViewModel
的獲取。
6.MVVM 存在的問(wèn)題
- 數(shù)據(jù)綁定使得 Bug 很難被調(diào)試。你看到界面異常了,有可能是你 View 的代碼有 Bug,也可能是 Model 的代碼有問(wèn)題。數(shù)據(jù)綁定使得一個(gè)位置的 Bug 被快速傳遞到別的位置,要定位原始出問(wèn)題的地方就變得不那么容易了。
- 對(duì)于過(guò)大的項(xiàng)目,數(shù)據(jù)綁定需要花費(fèi)更多的內(nèi)存。
- 存在一定的學(xué)習(xí)成本和引入更多的三方庫(kù)(RAC等等),代碼邏輯更復(fù)雜
7.總結(jié)
MVC
的設(shè)計(jì)模式也并非是病入膏肓,無(wú)藥可救的架構(gòu),最起碼目前MVC
設(shè)計(jì)模式仍舊是iOS
開發(fā)的主流框架,存在即合理。針對(duì)文章所述的弊端,我們依舊有許多可行的方法去避免和解決,從而打造一個(gè)輕量級(jí)的ViewController
。MVVM
是MVC
的升級(jí)版,完全兼容當(dāng)前的MVC
架構(gòu),MVVM
雖然促進(jìn)了UI 代碼與業(yè)務(wù)邏輯的分離,一定程度上減輕了ViewController
的臃腫度,但是View
和ViewModel
之間的數(shù)據(jù)綁定使得MVVM
變得復(fù)雜和難用了,如果我們不能更好的駕馭兩者之間的數(shù)據(jù)綁定,同樣會(huì)造成Controller
代碼過(guò)于復(fù)雜,代碼邏輯不易維護(hù)的問(wèn)題。一個(gè)輕量級(jí)的
ViewController
是基于MVC
和MVVM
模式進(jìn)行代碼職責(zé)的分離而打造的。MVC
和MVVM
有優(yōu)點(diǎn)也有缺點(diǎn),但缺點(diǎn)在他們所帶來(lái)的好處面前時(shí)不值一提的。他們的低耦合性,封裝性,可測(cè)試性,可維護(hù)性和多人協(xié)作便利大大提高了開法效率。同時(shí),我們需要保持的是一個(gè)擁抱變化的心,以及理性分析的態(tài)度。在新技術(shù)的面前,不盲從,也不守舊,一切的決策都應(yīng)該建立在認(rèn)真分析的基礎(chǔ)上,這樣才能應(yīng)對(duì)技術(shù)的變化。
在選擇架構(gòu)的方向上,請(qǐng)記住這句話:沒(méi)有最好的架構(gòu),只有最適合的。
Demo下載:
https://github.com/gaoyuGood/MVVM-RAC
參考文獻(xiàn):
iOS 關(guān)于MVC和MVVM設(shè)計(jì)模式的那些事
mvc和mvvm的區(qū)別
淺談 MVC、MVP 和 MVVM 架構(gòu)模式
被誤解的 MVC 和被神化的 MVVM
iOS開發(fā)之RAC(一)初級(jí)篇
最快讓你上手ReactiveCocoa之基礎(chǔ)篇