iOS開發(fā)之MVVM+RAC架構(gòu)模式

在說(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è)橋梁,建立起ModelView的連接,但實(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)題。

iOS中的M-VC

舉個(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ī)制、notificationsKVO

詳細(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

  • MVVMMVC的升級(jí)版,完全兼容當(dāng)前的MVC架構(gòu),MVVM雖然促進(jìn)了UI 代碼與業(yè)務(wù)邏輯的分離,一定程度上減輕了ViewController的臃腫度,但是ViewViewModel之間的數(shù)據(jù)綁定使得MVVM變得復(fù)雜和難用了,如果我們不能更好的駕馭兩者之間的數(shù)據(jù)綁定,同樣會(huì)造成Controller 代碼過(guò)于復(fù)雜,代碼邏輯不易維護(hù)的問(wèn)題。

  • 一個(gè)輕量級(jí)的ViewController是基于MVCMVVM模式進(jìn)行代碼職責(zé)的分離而打造的。MVCMVVM有優(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ǔ)篇

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。