[譯]基于ReactiveCocoa的MVVM開發(fā)模式教程:Part1/2

此文是翻譯作品,原文見:http://www.raywenderlich.com/74106/mvvm-tutorial-with-reactivecocoa-part-1

此文是我學習過程中遇到的很好的文章槽华,因為搜不到翻譯版本嚷缭,因此自己翻譯了赏酥,希望能幫到大家碑定。同時翻譯的時候我也好好精進了一下我的markdown語法

你可能在Twitter上聽過這樣的笑話:

“iOS框架奕纫,大量View Controller的產(chǎn)生地” by Colin Campbell涩馆。

這在iOS開發(fā)者心中是個輕松地“戳”行施,但是我確信你已經(jīng)在練習中遇到過這些問題了——臃腫的,難以管理的View Controller魂那。

這個MVVM的開發(fā)教程用一個不同的模式來構建一個app蛾号,Model-View-View-ViewModel,或者簡稱MVVM涯雅,這個模式因為ReactiveCocoa的誕生更加方便鲜结,帶來了一個完美的MVC模式的替換模式,和一個輕便的活逆,易于管理的View Controller精刷!

通過這個MVVM教程,你要去建立一個簡單的搜索app叫做Flicker search蔗候,像下面的圖片一樣:

1.png

注意:這個教程是使用Objective-C開發(fā)的贬养,如果你要看我用swift開發(fā)的教程,點擊這里,在我的博客里面可以看到琴庵。

在你開始寫代碼前误算,是時候講一些理論知識了!

一個對ReactiveCocoa的簡單介紹

這個教程主要是關于MVVM的迷殿,并且假設你對ReactiveCocoa有一定的了解儿礼,如果你沒有用過ReactiveCocoa,我強烈建議你看我早一些的教程,這個教程會教給你很多。
ReactiveCocoa最核心的東西無疑是 signals庆寺,在RACSignal 這個類里面蚊夫。signals給事件發(fā)出一個流,這個流(stream)有三種類型: next懦尝、 completederror知纷。

運用這些簡單的模式,ReactiveCocoa 可以用來替代代理模式(delegate pattern)陵霉,觀察者模式(KVO)和 target-action pattern琅轧,以及更多。

用signal的API編寫出來的代碼更加均勻踊挠,因此更加容易閱讀乍桂,但是ReactiveCocoa真正的強大的地方在于是你對signals的高級操作,這些操作允許你進行復雜的過濾(filter),轉化(transformation)以及用簡單的方式協(xié)調(coordination)睹酌。

在MVVM的環(huán)境下权谁,ReactiveCocoa扮演了極其重要的角色,它提供了強大的粘合力在View和ViewModel中間憋沿,這些對你還有一點點的超前旺芽。

MVVM開發(fā)模式的介紹

MVVM——Model-View-ViewModel,在通常的理解中是一個設計的模式辐啄,他是MV家族的一個成員采章,這個家族包括MVC、MVP等等则披。

每一個MV家族中的模式開始關心如何將UI和業(yè)務邏輯分開共缕,因為這樣更便利于開發(fā)和測試洗出。

注意:如果想要深入了解開發(fā)設計模式士复,我推薦Eli’sAsh Furrow’s的文章。

了解MVVM的起源有助于你更加了解這個模式翩活。

MVC是第一個用戶界面設計模式( UI design pattern)阱洪,可以追溯到1970年代的Smalltalk language。下面這個圖說明了MVC的主要運作模式:

MVC.png

這個模式將用戶界面分為三種:

  • Model菠镇,用來呈現(xiàn)應用狀態(tài)冗荸。
  • View,由視圖控制器組成利耍。
  • Controller蚌本,處理用戶交互并且更新model。

MVC的一個重大問題令人十分困擾隘梨,這個概念很好很完美程癌,但是當經(jīng)常人們開始實現(xiàn)MVC的時候,Model-View-Controller看似圓形的關系轴猎,反過來嵌莉,他們合并成了一個可怕的巨大的麻煩。

不久之前Martin Fowler 向我們介紹了一個由MVC衍生出來的表現(xiàn)模式捻脖,并被微軟接受并流行開來锐峭。

MVVM.png

這個模式的核心是ViewModel,是一種特殊的Model可婶,用來展示應用中UI的狀態(tài)沿癞。

它包含了每一個UI控制器(Controller)的詳細狀態(tài)和屬性,例如矛渴,一個TextFeild當前的文字抛寝,或者一個按鈕的可否點擊的狀態(tài),它也展現(xiàn)了當前視圖的一系列動作,例如按鈕點擊或者手勢操作盗舰。

將VIewModel理解成為View的Model(model of the view)可以更好地幫你去思考ViewModel晶府。

MVVM遵循以下規(guī)則

  • 1.View用來展現(xiàn)VIewModel,但是VIewModel不能展現(xiàn)View钻趋。
  • 2.VIewModel用來展現(xiàn)Model川陆,但是也不可反過來。

如果你打破了任何這個規(guī)則蛮位,你的MVVM就錯了较沪!

這種規(guī)則的優(yōu)勢如下:

  • 1.更加輕量級的VIew層,所有業(yè)務邏輯都被移到ViewModel中失仁。
  • 2.更易于測試尸曼,你可以在沒有View的情況下啟動你的應用,大大提高了可測試性萄焦。

注意:測試視圖是眾所周知的困難控轿,因為測試運行的小的包含的代碼塊。通常拂封,控制器會在依賴于其他應用程序狀態(tài)的場景中添加和配置視圖茬射。這意味著,意義上的小測試冒签,可以成為一個脆弱而繁瑣的命題在抛。

因此,你可能會想提出一個問題萧恕,如果只是View可以展現(xiàn)VIewModel刚梭,而ViewModel不能反過來展現(xiàn)View的話,那么ViewModel如何更新View呢票唆?啊哈F佣痢!這就是MVVM的秘訣了惰说!

MVVM和數(shù)據(jù)綁定(Data Binding)

MVVM模式依賴于數(shù)據(jù)綁定磨德,一個框架級的功能,自動連接對象屬性的用戶界面控件吆视。

有一個例子典挑,在微軟的WPF框架,下面一個例子將TextField的文本和ViewModel的Username綁定。

  <TextField Text=”{DataBinding Path=Username,Mode=TwoWay}”/>

WPF的框架將這兩個成員變量“綁定”啦吧。

這個雙向的綁定確保了ViewModel的Username的改變同時TextFeild的文本也改變您觉,反之亦然,用戶的輸入也將改變ViewModel中的參數(shù)值授滓。

另一個例子琳水,基于web的流行的一個MVVM框架Knockout, 你可以發(fā)現(xiàn)兩個框架中數(shù)據(jù)綁定的相同的特點肆糕。

<input data-bind=”value: username”/>

上面的綁定將HTML的元素和JavaScript的模型綁定。

不幸的是在孝,iOS缺少一個數(shù)據(jù)綁定框架诚啃,但是這就是ReactiveCocoa所充當?shù)摹澳z水”作用。

具體從iOS開發(fā)的角度去看MVVM私沮,ViewController和它相關的UI——不論是xib始赎、storyboard或者是代碼組成的視圖(View):

MVVMReactiveCocoa.png

ReactiveCocoa將兩者綁定起來。

注意:對于UI的各種實現(xiàn)方式仔燕,我高度推薦Martin Fowler的GUI Architectures article造垛。

你學到了足夠的理論知識了嗎?如果沒有晰搀,請回頭去再看一遍五辽。當然,如果你學得夠好了外恕,那么現(xiàn)在是時候開始創(chuàng)造你自己的ViewModel了杆逗。

開始項目準備

首先下載這個開始工程

這個項目使用CocoaPods去管理依賴庫(如果你不知道CocoaPods,我們這里有個教程),運行pod install去安裝依賴庫吁讨,確認你看到了一下輸出:

  $ pod install
  Analyzing dependencies
  Downloading dependencies
  Installing LinqToObjectiveC (2.0.0)
  Installing ReactiveCocoa (2.1.8)
  Installing SDWebImage (3.6)
  Installing objectiveflickr (2.0.4)
  Generating Pods project
  Integrating client project

你會學到每個依賴庫是干嗎的髓迎。

本教程的開始工程包含了一個View峦朗,通過xib實現(xiàn)建丧,打開RWTFlickrSearch.xcworkspace,并且運行,然后你會看到以下頁面:

first-launch.jpg

花一點時間去熟悉這個項目結構:


EmptyInterface.png

Model和VIewModel的groups都是空的波势,你要為這兩個group添加文件翎朱,項目已含有的文件是做這些的:

是時候開始寫你的第一個view model 了!

你的第一個ViewModel

在ViewModel這個group里添加一個新的類凛忿,將之命名為RWTFlickrSearchViewModel并且使他繼承NSObject澈灼。

打開它并在頭文件添加下面的聲明:

  @interface RWTFlickrSearchViewModel : NSObject
  @property (strong, nonatomic) NSString *searchText;
  @property (strong, nonatomic) NSString*title;
  @end

searchText提供一個字符串顯示在textfield上,成員變量title提供在navigation bar上顯示的標題店溢。

注意 :為了更容易的理解項目結構叁熔,View和ViewModel用了相同的名字和不同的后綴,例如:RWTFlickrSearch-ViewModel
RWTFlickrSearch-ViewController床牧。

打開 RWTFlickrSearchViewModel.m 并且添加如下代碼

  @implementation RWTFlickrSearchViewModel
   - (instancetype)init { 
      self = [super init];
      if (self) { 
         [self initialize];
     }
         return self;
   } 
   - (void)initialize { 
      self.searchText = @"search text";               
      self.title = @"Flickr Search";
  }
  @end

這段代碼初始化了這個ViewModel荣回。

下一步是講如何將ViewModel和View關聯(lián)到一起,記住View和ViewModel的關聯(lián)戈咳,因此就需要在View中給對應的ViewModel添加一個相關的實例化方法心软。

注意:在這個教程管我們的Controller叫做”Views“壕吹,這筆“View”在MVVM更多語義。和UIKit使用的默認名不同删铃。

打開 RWTFlickrSearchViewController.h 并聲明ViewModel的頭文件耳贬。
#import "RWTFlickrSearchViewModel.h"
然后加入下面的實例化方法

@interface RWTFlickrSearchViewController : UIViewController
  - (instancetype)initWithViewModel:(RWTFlickrSearchViewModel *)viewModel;
@end

RWTFlickrSearchViewController.m中添加一個私有變量

@property (weak, nonatomic) RWTFlickrSearchViewModel *viewModel;

接下來實現(xiàn)init方法

- (instancetype)initWithViewModel:(RWTFlickrSearchViewModel         *)viewModel {
  self = [super init];
  if (self ) {
    _viewModel = viewModel;
  }
  return self;
}

注意:這是個弱引用(弱指針),View引用了ViewModel猎唁,但沒有擁有它效拭。

viewDidLoad的最后加上下面代碼

[self bindViewModel]; 

下面是這個方法的實現(xiàn)

- (void)bindViewModel {
  self.title = self.viewModel.title;
  self.searchTextField.text = self.viewModel.searchText;
}

上面的代碼將會在UI初始化和ViewModel狀態(tài)在VIew上應用的時候運行。
最后一步是實例化ViewModel胖秒,并在View中應用缎患。
viewDidLoad中添加以下

#import "RWTFlickrSearchViewModel.h"

加一個私有變量(在文件頂部的類擴展名內)。

@property (strong, nonatomic) RWTFlickrSearchViewModel *viewModel;

你會發(fā)現(xiàn)已經(jīng)有了一個createInitialViewController方法阎肝,更新他的實現(xiàn)方法:

- (UIViewController *)createInitialViewController {
  self.viewModel = [RWTFlickrSearchViewModel new];
  return [[RWTFlickrSearchViewController alloc]initWithViewModel:self.viewModel];
}

這將創(chuàng)建一個新的ViewModel實例挤渔,然后它返回View。這是應用程序的導航控制器的初始視圖风题。

恭喜你判导,這是你的第一個ViewModel。我得請你控制住你的興奮沛硅!這里還有很多要學習眼刃。
你可能已經(jīng)注意到了你沒有使用任何ReactiveCocoa呢。在其目前的形式摇肌,任何用戶進入搜索文本字段將不會反映在ViewModel擂红。

檢測有效搜索狀態(tài)

在這一部分中,你將使用ReactiveCocoa綁定ViewModel和View的搜索框和按鈕在一起围小,更新RWTFlickrSearchViewController.m中的bindViewModel方法如下

- (void)bindViewModel {
  self.title = self.viewModel.title;
  RAC(self.viewModel, searchText) = self.searchTextField.rac_textSignal;
}

我們來加一個ReactiveCocoa 中UITextFeild的分類的方法rac_textSignal昵骤,它是一個信號,每次文本字段更新時肯适,將發(fā)出包含當前文本的一個事件变秦,RAC宏是一個綁定,上面代碼更新了ViewModel中的searchText對象的值框舔,它隨著rac_textSignal響應蹦玫。
總之,上面的代碼保證了searchText的值總是UI中的最新的值刘绣。如果上面的寫法讓你感到陌生樱溉,你真的應該重新學習一下ReactiveCocoa tutorials這個教程!

如果用戶輸入的文本是有效的额港,則只能啟用搜索按鈕饺窿。這里的輸入規(guī)則是,他們必須輸入超過三個字符移斩,然后才能執(zhí)行搜索肚医。
RWTFlickrSearchViewModel.m加入下面代碼

#import <ReactiveCocoa/ReactiveCocoa.h>

更新方法initialize:

- (void)initialize {
  self.title = @"Flickr Search";
  RACSignal *validSearchSignal =
    [[RACObserve(self, searchText)
    map:^id(NSString *text) {
     return @(text.length > 3);
    }]
    distinctUntilChanged];

  [validSearchSignal subscribeNext:^(id x) {
      NSLog(@"search text is valid %@", x);
   }];
 }

編譯绢馍,運行并在TextFeild輸入一些文字。每次文本在有效或無效狀態(tài)之間轉換時肠套,都會看到日志消息:

2014-05-27 18:03:26.299 RWTFlickrSearch[13392:70b] search text is valid 0
2014-05-27 18:03:28.379 RWTFlickrSearch[13392:70b] search text is valid 1
2014-05-27 18:03:29.811 RWTFlickrSearch[13392:70b] search text is valid 0

上面的代碼使用 RACObserve宏創(chuàng)建了一個ViewModel中的 searchText的信號舰涌。map操作將文本流轉換為真值和假值。最后你稚, distinctuntilchanges是用來確保該信號只在狀態(tài)變化的時候傳遞值瓷耙。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市刁赖,隨后出現(xiàn)的幾起案子搁痛,更是在濱河造成了極大的恐慌,老刑警劉巖宇弛,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鸡典,死亡現(xiàn)場離奇詭異,居然都是意外死亡枪芒,警方通過查閱死者的電腦和手機彻况,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來舅踪,“玉大人纽甘,你說我怎么就攤上這事〕槁担” “怎么了悍赢?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長咬展。 經(jīng)常有香客問我泽裳,道長瞒斩,這世上最難降的妖魔是什么破婆? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮胸囱,結果婚禮上祷舀,老公的妹妹穿的比我還像新娘。我一直安慰自己烹笔,他們只是感情好裳扯,可當我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著谤职,像睡著了一般饰豺。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上允蜈,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天冤吨,我揣著相機與錄音蒿柳,去河邊找鬼。 笑死漩蟆,一個胖子當著我的面吹牛垒探,可吹牛的內容都是我干的。 我是一名探鬼主播怠李,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼圾叼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了捺癞?” 一聲冷哼從身側響起夷蚊,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎髓介,沒想到半個月后撬码,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡版保,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年呜笑,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片彻犁。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡叫胁,死狀恐怖,靈堂內的尸體忽然破棺而出汞幢,到底是詐尸還是另有隱情驼鹅,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布森篷,位于F島的核電站输钩,受9級特大地震影響,放射性物質發(fā)生泄漏仲智。R本人自食惡果不足惜买乃,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望钓辆。 院中可真熱鬧剪验,春花似錦、人聲如沸前联。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽似嗤。三九已至啸臀,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間烁落,已是汗流浹背乘粒。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工席揽, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人谓厘。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓幌羞,卻偏偏與公主長得像,于是被迫代替她去往敵國和親竟稳。 傳聞我的和親對象是個殘疾皇子属桦,可洞房花燭夜當晚...
    茶點故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內容