1. 程式人生 > >MVVM With ReactiveCocoa

MVVM With ReactiveCocoa

MVVM 是一種軟體架構模式,它是 Martin FowlerPresentation Model 的一種變體,最先由微軟的架構師 John Gossman 在 2005 年提出,並應用在微軟的 WPFSilverlight 軟體開發中。MVVM 衍生於 MVC ,是對 MVC 的一種演進,它促進了 UI 程式碼與業務邏輯的分離。

說明:本文將採用理論與實踐相結合的方式,重點介紹一個使用 MVVMRAC 開發的 iOS 開源專案 MVVMReactiveCocoa ,目的是希望能為你實踐 MVVM 提供幫助。不過,在正式開始介紹正文之前,請你先思考以下三個問題:

  • MVCMVVM
    有什麼異同點,MVCMVVM 是怎樣演進的;
  • RACMVVM 中扮演什麼樣的角色,MVVM 是否一定要結合 RAC 使用;
  • 如何將一個現有的 MVC 應用轉變成一個 MVVM 應用,有哪些需要注意的地方。

帶著以上問題,我們一起進入正文。

名詞解釋:本文中的 RACReactiveCocoa 的縮寫。

MVC

MVCiOS 開發中使用最普遍的架構模式,同時也是蘋果官方推薦的架構模式。MVC 代表的是 Model–view–controller ,它們之間的關係如下:

是的,MVC 看上去棒極了,model 代表資料,view 代表 UI ,而 controller

則負責協調它們兩者之間的關係。然而,儘管從技術上看 viewcontroller 是相互獨立的,但事實上它們幾乎總是結對出現,一個 view 只能與一個 controller 進行匹配,反之亦然。既然如此,那我們為何不將它們看作一個整體呢:

因此,M-VC 可能是對 iOS 中的 MVC 模式更為準確的解讀。在一個典型的 MVC 應用中,controller 由於承載了過多的邏輯,往往會變得臃腫不堪,所以 MVC 也經常被人調侃成 Massive View Controller

iOS architecture, where MVC stands for Massive View Controller.

坦白說,有一部分邏輯確實是屬於 controller 的,但是也有一部分邏輯是不應該被放置在 controller 中的。比如,將 model 中的 NSDate 轉換成 view 可以展示的 NSString 等。在 MVVM 中,我們將這些邏輯統稱為展示邏輯。

MVVM

因此,一種可以很好地解決 Massive View Controller 問題的辦法就是將 controller 中的展示邏輯抽取出來,放置到一個專門的地方,而這個地方就是 viewModel 。其實,我們只要在上圖中的 M-VC 之間放入 VM ,就可以得到 MVVM 模式的結構圖:

從上圖中,我們可以非常清楚地看到 MVVM 中四個元件之間的關係。:除了 viewviewModelmodel 之外,MVVM 中還有一個非常重要的隱含元件 binder

  • view :由 MVC 中的 viewcontroller 組成,負責 UI 的展示,繫結 viewModel 中的屬性,觸發 viewModel 中的命令;
  • viewModel :從 MVCcontroller 中抽取出來的展示邏輯,負責從 model 中獲取 view 所需的資料,轉換成 view 可以展示的資料,並暴露公開的屬性和命令供 view 進行繫結;
  • model :與 MVC 中的 model 一致,包括資料模型、訪問資料庫的操作和網路請求等;
  • binder :在 MVVM 中,宣告式的資料和命令繫結是一個隱含的約定,它可以讓開發者非常方便地實現 viewviewModel 的同步,避免編寫大量繁雜的樣板化程式碼。在微軟的 MVVM 實現中,使用的是一種被稱為 XAML 的標記語言。

ReactiveCocoa

儘管,在 iOS 開發中,系統並沒有提供類似的框架可以讓我們方便地實現 binder 功能,不過,值得慶幸的是,GitHub 開源的 RAC ,給了我們一個非常不錯的選擇。

iOSMVVM 實現中,我們可以使用 RAC 來在 viewviewModel 之間充當 binder 的角色,優雅地實現兩者之間的同步。此外,我們還可以把 RAC 用在 model 層,使用 Signal 來代表非同步的資料獲取操作,比如讀取檔案、訪問資料庫和網路請求等。說明RAC 的後一個應用場景是與 MVVM 無關的,也就是說,我們同樣可以在 MVCmodel 層這麼用。

小結

綜上所述,我們只要將 MVC 中的 controller 中的展示邏輯抽取出來,放置到 viewModel 中,然後通過一定的技術手段,比如 RAC 來同步 viewviewModel ,就完成了 MVCMVVM 的轉變。

Talk is cheap. Show me the code.

下面,我們直接上程式碼,一起來看一個 MVC 模式轉換成 MVVM 模式的示例。首先是 model 層的程式碼 Person

Objective-C
12345678910 @interface Person : NSObject-(instancetype)initwithSalutation:(NSString*)salutation firstName:(NSString*)firstName lastName:(NSString*)lastName birthdate:(NSDate*)birthdate;@property(nonatomic,copy,readonly)NSString*salutation;@property(nonatomic,copy,readonly)NSString*firstName;@property(nonatomic,copy,readonly)NSString*lastName;@property(nonatomic,copy,readonly)NSDate*birthdate;@end

然後是 view 層的程式碼 PersonViewController ,在 viewDidLoad 方法中,我們將 Person 中的屬性進行一定的轉換後,賦值給相應的 view 進行展示:

Objective-C
12345678910111213 -(void)viewDidLoad{[superviewDidLoad];if(self.model.salutation.length>0){self.nameLabel.text=[NSString stringWithFormat:@"%@ %@ %@",self.model.salutation,self.model.firstName,self.model.lastName];}else{self.nameLabel.text=[NSString stringWithFormat:@"%@ %@",self.model.firstName,self.model.lastName];}NSDateFormatter*dateFormatter=[[NSDateFormatteralloc] init];[dateFormatter setDateFormat:@"EEEE MMMM d, yyyy"];self.birthdateLabel.text=[dateFormatter stringFromDate:model.birthdate];}

接下來,我們引入一個 viewModel ,將 PersonViewController 中的展示邏輯抽取到這個 PersonViewModel 中:

Objective-C
12345678910111213141516171819202122232425262728293031 @interface PersonViewModel : NSObject-(instancetype)initWithPerson:(Person*)person;@property(nonatomic,strong,readonly)Person*person;@property(nonatomic,copy,readonly)NSString*nameText;@property(nonatomic,copy,readonly)NSString*birthdateText;@end@implementationPersonViewModel-(instancetype)initWithPerson:(Person*)person{self=[superinit];if(self){_person=person;if(person.salutation.length>0){_nameText=[NSString stringWithFormat:@"%@ %@ %@",self.person.salutation,self.person.firstName,self.person.lastName];}else{_nameText=[NSString stringWithFormat:@"%@ %@",self.person.firstName,self.person.lastName];}NSDateFormatter*dateFormatter=[[NSDateFormatteralloc] init];[dateFormatter setDateFormat:@"EEEE MMMM d, yyyy"];_birthdateText=[dateFormatter stringFromDate:person.birthdate];}returnself;}@end

最終,PersonViewController 將會變得非常輕量級:

Objective-C
123456 -(void)viewDidLoad{[superviewDidLoad];self.nameLabel.text=self.viewModel.nameText;self.birthdateLabel.text=self.viewModel.birthdateText;}

怎麼樣?其實 MVVM 並沒有想像中的那麼難吧,而且更重要的是它也沒有破壞 MVC 的現有結構,只不過是移動了一些程式碼,僅此而已。好了,說了這麼多,那 MVVM 相比 MVC 到底有哪些好處呢?我想,主要可以歸納為以下三點:

  • 由於展示邏輯被抽取到了 viewModel 中,所以 view 中的程式碼將會變得非常輕量級;
  • 由於 viewModel 中的程式碼是與 UI 無關的,所以它具有良好的可測試性;
  • 對於一個封裝了大量業務邏輯的 model 來說,改變它可能會比較困難,並且存在一定的風險。在這種場景下,viewModel 可以作為 model 的介面卡使用,從而避免對 model 進行較大的改動。

通過前面的示例,我們對第一點已經有了一定的感觸;至於第三點,可能對於一個複雜的大型應用來說,才會比較明顯;下面,我們還是使用前面的示例,來直觀地感受下第二點好處:

Objective-C
123456789101112131415161718192021222324 SpecBegin(Person)NSString*salutation=@"Dr.";NSString*firstName=@"first";NSString*lastName=@"last";NSDate*birthdate=[NSDate dateWithTimeIntervalSince1970:0];it(@"should use the salutation available. ",^{Person*person=[[Personalloc] initWithSalutation:salutation firstName:firstName lastName:lastName birthdate:birthdate];PersonViewModel*viewModel=[[PersonViewModelalloc] initWithPerson:person];expect(viewModel.nameText).to.equal(@"Dr. first last");});it(@"should not use an unavailable salutation. ",^{Person*person=[[Personalloc] initWithSalutation:nil firstName:firstName lastName:lastName birthdate:birthdate];PersonViewModel*viewModel=[[PersonViewModelalloc] initWithPerson:person];expect(viewModel.nameText).to.equal(@"first last");});it(@"should use the correct date format. ",^{Person*person=[[Personalloc] initWithSalutation:nil firstName:firstName lastName:lastName birthdate:birthdate];PersonViewModel*viewModel=[[PersonViewModelalloc] initWithPerson:person];expect(viewModel.birthdateText).to.equal(@"Thursday January 1, 1970");});SpecEnd

對於 MVVM 來說,我們可以把 view 看作是 viewModel 的視覺化形式,viewModel 提供了 view 所需的資料和命令。因此,viewModel 的可測試性可以幫助我們極大地提高應用的質量。

MVVMReactiveCocoa

接下來,我們進入本文的第二部分,重點介紹一個使用 MVVMRAC 開發的開源專案 MVVMReactiveCocoa說明,本文將主要介紹這個應用的架構和設計思路,希望可以為你實踐 MVVM 提供一個真實的參考案例,有些架構並非是 MVVM 所必須的,而是我們為了更順暢地使用 MVVM 而引入的,特別是 ViewModel-Based Navigation 。所以,請你在實踐的過程中能夠結合自身應用的實際情況做出相應的取捨,靈活處理。最後,我們將以登入介面為例,一起探討下 MVVM 的實踐思路。

說明,以下內容均基於 MVVMReactiveCocoav2.1.1 標籤進行展開,並且對部分無關程式碼做了刪減。

類圖

為了方便我們從巨集觀上了解 MVVMReactiveCocoa 的整體結構,我們先來看看它的類圖:

MVVMReactiveCocoa-v2.1.1

從上圖中,我們可以看到,在 MVVMReactiveCocoa 中主要有兩大繼承體系:

  • 用藍色標識出來的 viewModel 的繼承體系,基類為 MRCViewModel
  • 用紅色標識出來的 view 的繼承體系,基類為 MRCViewController

除了提供與系統基類 UIViewController 相對應的基類 MRCViewModel/MRCViewController 外,還提供了與系統基類 UITableViewControllerUITabBarController 相對應的基類 MRCTableViewModel/MRCTableViewControllerMRCTabBarViewModel/MRCTabBarController ,其中基類 MRCTableViewModel/MRCTableViewController 的使用最為普遍。

說明,之所以通過基類的方式來組織 MVVMReactiveCocoa ,一方面是因為主要開發者只有我一個人,這個方案非常容易實施;另一方面是因為通過基類的方式可以儘可能簡單地實現程式碼重用,提高開發效率。

服務匯流排

經過前面的探討,我們已經知道了 MVVM 中的 viewModel 的主要職責就是從 model 層獲取 view 所需的資料,並且將這些資料轉換成 view 能夠展示的形式。因此,為了方便 viewModel 層呼叫 model 層中的所有服務,並且統一管理這些服務的建立,我使用抽象工廠模式將 model 層的所有服務集中管理了起來,結構圖如下:

從上圖中,我們可以看出,在服務匯流排類 MRCViewModelServices/MRCViewModelServicesImpl 中,主要包括以下三個方面的內容:

  • 應用自有的服務類,用柚黃色進行了標識,包括 MRCAppStoreService/MRCAppStoreServiceImplMRCRepositoryService/MRCRepositoryServiceImpl 兩個服務類;
  • 第三方 GitHub 提供的 API 框架,用天藍色進行了標識,主要包括 OCTClient 服務類;
  • 應用的導航服務,用藻綠色進行了標識,包括 MRCNavigationProtocol 協議和實現類 MRCViewModelServicesImpl 等。

其中,前兩者都是以訊號的形式對 viewModel 層提供服務,代表非同步的網路請求等資料獲取操作,而我們在 viewModel 層則可以通過訂閱訊號的形式獲取到所需的資料。此外,服務匯流排還實現了 MRCNavigationProtocol 協議,它的內容如下:

Objective-C
123456789101112131415 @protocolMRCNavigationProtocol<NSObject>-(void)pushViewModel:(MRCViewModel*)viewModel animated:(BOOL)animated;-(void)popViewModelAnimated:(BOOL)animated;-(void)popToRootViewModelAnimated:(BOOL)animated;-(void)presentViewModel:(MRCViewModel*)viewModel animated:(BOOL)animated completion:(VoidBlock)completion;-(void)dismissViewModelAnimated:(BOOL)animated completion:(VoidBlock)completion;-(void)resetRootViewModel:(MRCViewModel*)viewModel;@end

看上去是不是有點眼熟?是的,MRCNavigationProtocol 協議其實就是參照系統的導航操作定義出來的,用來實現 ViewModel-Based 的導航服務。注意,服務匯流排類 MRCViewModelServicesImpl 其實並沒有真正實現 MRCNavigationProtocol 協議中宣告的操作,只不過是實現了一些空操作而已:

相關推薦

MVVM With ReactiveCocoa

MVVM 是一種軟體架構模式,它是 Martin Fowler 的 Presentation Model 的一種變體,最先由微軟的架構師 John Gossman 在 2005 年提出,並應用在微軟的 WPF 和 Silverlight 軟體開發中。MVVM 衍生於 MVC ,

MVVMReactiveCocoa 的運用(1)

MVVM和資料繫結 MVVM模式依賴於資料繫結,能自動將物件屬性和UI controls相聯絡是其框架級的特性. 舉個例子,在微軟的WPF框架裡,ViewModel將TextField裡的Text屬性和Username屬性繫結,如下所示: Objective-C

MVVMReactiveCocoa 的運用(2)

繫結,繫結,繫結(重要的實情說三遍) RACCommand能實時地更新search按鈕的狀態,但是時候來處理activity indicator的可見狀態了. RACCommand擁有一個執行的屬性,它是用來表示命令開始和結束執行時反應真假事件的訊號量.你可以通過這個訊號量來反映程式中當前命令執行的狀態

iOS開發之ReactiveCocoa下的MVVM

最近工作比較忙,但還是出來更新部落格了,今天給大家分享一些ReactiveCocoa以及MVVM的一些東西,幹活還是比較足的。在之前發表過一篇博文,名字叫做,大體上講的就是使用Block回撥的方式實現MVVM的。在寫上篇文章時也知道有ReactiveCocoa這個函式響應式程式設計的框架,並且有許多人用它來更

RxJava: Android MVVM App structure with Retrofit

Tips for RxJavaI assume you already know few things about RxJava. I learnt from different sources of information such as: RxJava documentation, @Dan Lew Co

乾貨集中營-ReactiveCocoa+RXSwift+MVVM

學習函式響應式程式設計已經接近兩個月的時間。說實話堅持下來實在不易。兩個月的時間看過近150篇博文,算下來啃下來一本千頁的技術書籍也差不多。不過隨著知識面的拓廣,學習起來也更加順利。本篇文章主要整理下自己收集的學習路線。其中包括了函數語言程式設計的思想,ReactiveCoco

iOS開發之ReactiveCocoa下的MVVM(乾貨分享)

最近工作比較忙,但還是出來更新部落格了,今天給大家分享一些ReactiveCocoa以及MVVM的一些東西,幹活還是比較足的。在之前發表過一篇博文,名字叫做,大體上講的就是使用Block回撥的方式實現MVVM的。在寫上篇文章時也知道有ReactiveCocoa這個函式響應式程式

基於ReactiveCocoaMVVM設計的購物車基本操作實現程式碼解析

購物車單選全選價格計算數量增刪等等操作…RAC皆統統搞定.就是這麼cool~ 開始之前你需要了解的 配置CocoaPods gem install cocoapods ##使用RVM安裝的Ruby不需要sudo

MVVM模式初體驗(使用ReactiveCocoa獲取網路資料)

使用RAC也有一段時間了,由於此前的專案都是使用的MVC模式,網路請求都封裝在固定的模組中,抽取出來十分不方便,所以到目前為止並沒有涉及到使用RAC去做獲取網路請求的情景。 近期,著手重構目前手上的專案,準備給臃腫的Controller瘦身,MVVM貌似是

(轉) Learning Deep Learning with Keras

trees create pda sse caffe latex .py encode you Learning Deep Learning with Keras Piotr Migda? - blog Projects Articles Publications Res

hihoCoder #1454 : Rikka with Tree II

return 一段 har 節點 sla include turn typedef ems Description 一個\(n\)個節點的樹,先根遍歷為\(1...n\)。已知兩個數組,一個數組表示是否是葉節點,另一個數組表示十分有右兄弟節點...‘?‘表示未知,求方案數

Local Authentication Using Challenge Response with Yubikey for CentOS 7

fail ins path api -m ica all use debug Connect Yubikey ,then initialize YubiKey slot 2: ykpersonalize -2 -ochal-resp -ochal-hmac -ohmac

MVVM Light須要註意的10個問題

ram prop 可能 rop cst -a 計時 install 檢查 MVVM Light須要註意的10個問題 從使用XAML技術基礎開始(實際上並非非常久曾經)。我便關註MVVM(Model – View – ViewModel)模式。偶然接觸到MVVM Ligh

here with you

vertical can more lose 音樂 and -a ember music Here With You - Asher Book To all my friends對我所有好友來講The night is young夜未央The music‘s loud樂未殤

[SCSS] Write similar classes with the SCSS @for Control Directive

att oop enc rem coo tro from mil for Writing similar classes with minor variations, like utility classes, can be a pain to write and upda

with ..do 簡化語句使用

ima col img class color 訪問 簡化 times mes 訪問對象的簡化語句可以用with; 通常訪問對象的屬性和方法需要在前面加上對象的名稱; 如: procedure TForm2.btn1Click(Sender: TObject); begi

Ng第二課:單變量線性回歸(Linear Regression with One Variable)

dll oba vcf 更多 dba cfq dpf gis avd 二、單變量線性回歸(Linear Regression with One Variable) 2.1 模型表示 2.2 代價函數 2.3 代價函數的直觀理解 2.4 梯度下降

【論文閱讀-REC】<<Recommending music on Spotify with deep learing>>閱讀

play ring 來源 調整 能力 表達 layers 書籍 訓練 1、協同過濾 協同過濾不使用item的具體信息,因此可適用性很強,在書籍、電影、音樂上都可用; 協同過濾不適用item的具體信息,因此強者愈強; 冷啟動問題無法解決 2、基於內容的推薦 使用聲音信號推薦

[CSS] Draw Simple Icons with CSS

cnblogs elements chang pre active pla com man simple Using pseudo-elements like ::before and ::after we can draw some simple icons withou

解決Problem with writing the data, class java.util.ArrayList, ContentType: application/xml

writing 數據庫 今天,在使用cxf讀取內網數據庫的數據時,報以下一個錯誤Problem with writing the data, class java.util.ArrayList, ContentType: application/xml以上錯誤提示我們,在寫入數據時有錯誤,最後經檢查