MVVM 與 ReactiveCocoa 的運用(2)

發表於2016-07-05
  • 繫結,繫結,繫結(重要的實情說三遍)

RACCommand能實時地更新search按鈕的狀態,但是時候來處理activity indicator的可見狀態了.
RACCommand擁有一個執行的屬性,它是用來表示命令開始和結束執行時反應真假事件的訊號量.你可以通過這個訊號量來反映程式中當前命令執行的狀態.
在RWTFlickrSearchViewController.m的bindViewModel的尾部新增:

以上程式碼用來將UIApplication中的networkActivityIndicatorVisible屬性和命令執行訊號繫結.用來保證當命令執行時,小的網路啟用狀態標誌在status bar裡顯示.
下一步,新增:

當指令執行後,載入標誌將會被隱藏;這和你剛剛繫結的屬性相反.
ReactiveCocoa已經為我們提供了不執行的相反的訊號.最後,新增如下程式碼:

上面的程式碼用來保證當命令執行時鍵盤會隱藏.executionSignals屬性用來在命令執行時實時地發出訊號.
這是個signals中的一個signal屬性(前面教程裡有介紹).當一個新的命令執行的時候就會被建立和執行,隱藏鍵盤.
執行程式,來驗證以上程式碼的執行.

  • Model呢?

到現在為止,你已經定義了一個View(RWTFlickrSearchViewController)和ViewModel(RWTFlickrSearchViewModel),但是,怎麼木有Model呢?
答案很簡單:就是還沒有啊!
當前app使用者點選搜尋按鈕後就會執行命令,但卻沒有實現什麼.
我們需要實現的是利用當前輸入的搜尋文字通過ViewModel在Flickr進行搜尋,繼而返回相匹配的圖片列表.
你可以將此邏輯直接放在ViewModel裡,但相信我,你會後悔的!如果是個view controller,我到時強烈你這麼做.
View Model擁有UI狀態的屬性,而且還能夠執行命令(經常為UI上的動作方法).通過使用者的互動來管理改變UI狀態.
然而,並不表示這些互動實際的業務邏輯應該在View Model裡面.而這應該是Model的工作.
下一步,將會給應用增加Model層.
在Model group裡新增一個名為RWTFlickrSearch的新協議並提供瞭如下的方法:

這個協議定義了Model層的初始方法,用來將負責搜尋Flickr的任務從ViewModel裡移出.
接下來,在同一group裡建立一個名為RWTFlickrSearchImpl的NSObject的子類.並使其遵從剛才的協議:

在RWTFlickrSearchImpl.m裡新增以下程式碼:

是不是覺得似曾相識?如果是的,那是因為這個同一’虛擬’的實現曾經位於ViewModel裡.
下一步是要在ViewModel裡使用Model層.在ViewModel group裡新增一個名為RWTViewModelServices的新協議:

這個協議定義了ViewModel獲得對RWTFlickrSearch協議引用的方法.
在RWTFlickrSearchViewModel.h匯入這個新協議:

更新initializer來將它作為引數:

在RWTFlickrSearchViewModel.m裡新增一個類擴充套件和一個私有屬性來儲存對view model services的引用:

在同一檔案裡更新initializer:

以上用來儲存對services的引用.
最後,更新executeSearchSignal方法:

上面的方法代理了model來實現搜尋.
最後一步是將Model和ViewModel相連.
在RWTFlickrSearch專案的’root’ group裡新增一個名為RWTViewModelServiceImpl的NSObject的子類.在RWTViewModelServicesImpl.h裡新增遵從RWTViewModelServices協議:

在RWTViewModelServicesImpl.m實現:

這個類建立了一個RWTFlickrSearchImpl的例項,Model層服務Flickr搜尋,將其提供給ViewModel upon 請求.
最後,在RWTAppDelegate.m裡匯入:

新增一個私有屬性:

更新createInitialViewController方法:

執行程式,確保程式和之前執行的結果相似.
這並不是最令人興奮的變化,但花點時間來看看新程式碼的”形狀”.
Model層展示了’service’的ViewModel consumes.協議定義了這個服務介面,提供鬆耦合.
你可以用這個假設的服務實現用於單元測試.應用現在已經有了正確地Model-View-ViewModel結構.來小結下:

  1. Model提供應用業務邏輯實現的服務.在本應用中,它提供了Flickr搜尋的服務.
  2. ViewModel層提供了應用的檢視狀態.它提供了使用者互動以及來自檢視變化後Model層的事件.
  3. View層非常瘦,提供了ViewModel狀態變化後在檢視層的展示.
  4. Flickr搜尋

本章節,你將編寫一個真實的Flickr搜尋實現,事情開始變得令人興奮了呢;]
第一步是建立一個搜尋結果的model.
在Model group裡新增一個名為RWTFlickrPhoto的NSObject的子類,在介面裡新增三個屬性:

這個Model物件相當於Flickr搜尋API所返回的單張圖片.
在RWTFlickrPhoto.m新增如下方法:

這個方法可以在實現UI變化之前通過列印搜尋的結果來測試搜尋的實現.
接下來,新增另一個名為RWTFlickrSearchResults的NSObject子類的模型.在介面裡新增如下屬性:

用來儲存Flickr搜尋返回的圖片集合.
在RWTFlickrSearchResults.m的description方法裡新增:

現在開始編寫Flickr搜尋的程式碼嘍!
在RWTFlickrSearchImpl.m新增:

匯入了剛才你建立的Model,新增了一對CocoaPods新增的擴充套件依賴:

  • ObjectiveFlickr:這是個用Objective-C API 實現的Flickr API.用來處理授權和解析API返回的結果.使用此庫比直接呼叫Flickr API要簡單.
  • LingToObjectiveC:提供了流暢和豐富的查詢介面,過濾和轉換陣列和詞典.

仍然在RWTFlickrSearchImpl.m裡新增類的擴充套件:

使其遵從ObejectiveFlickr庫的OFFlickrAPIRequestDelegate協議並新增了一對私有屬性.你將很快看到他們的用法.
緊接著新增如下initializer:

以上程式碼建立了一個Flickr ‘context’來儲存API請求所需的ObjectiveFlickr資料.
你可以在Flickr App Garden來獲取Key.
ObjectiveFlickr API非常常規.你建立了一個API請求後返回結果成功或失敗是通過定義的OFFlickrAPIRequestDelegate方法來處理的.
當前API是通過你的Model層服務類即RWTFlickrSearch協議,有一個方法來通過文字搜尋字串來進行圖片搜尋的.
然而,待會你將新增一些另外的方法.
因此,你將要去從直接用通用的方法到使用這種基於代理的API訊號.
仍然在RWTFlickrSearchImpl.m檔案裡新增:

這個方法提供了一個API請求,詳見方法名和傳遞的引數,繼而通過block來傳遞結果.你將很快來了解它是如何工作的.
解析此方法,這裡有許多內容.下面是每步的釋義:

  1. createSignal方法建立了一個新訊號.傳遞訊號的block方法可以讓你處理髮送下一步、錯誤或者事件的完成訊號.
  2. ObjectivewFlickr請求被建立,這個請求的引用被儲存在請求set裡.如果沒有這段程式碼,OFFlickrAPIRequest將不被保留.
  3. rac_signalForSelector:fromProtocol方法從表示Flickr API 請求完成後的的代理方法裡建立一個訊號.
  4. 該訊號被繫結,結果變換和被髮送的結果作為訊號被建立.
  5. ObjectiveFlickr API 請求被使用.
  6. 當訊號被釋放後,block確保Flickr請求的引用被移除,避免記憶體漏洞.

現在我們來看下步驟4的更多細節:

rac_signalForSelector:fromProtocol:方法建立了successSignal,而且它還從代理方法裡建立訊號.
當代理方法被呼叫時,下一個事件包含方法引數的RACTuple被髮出.接著執行以下步驟:

  1. 一個map操作提取從flickrAPIRequest:didCompleteWithResponse:的第二個引數,代理方法中的詞典結果.
  2. block傳遞給此方法一個結果引數.你將很快看到如何將詞典轉換為模型物件.
  3. 最後,傳遞的結果被髮送給下一個事件,這個訊號完成.

最後來實現Flickr搜搜結果方法:

以上方法使用之前你新增的signalFromAPIMethod:arguments:transform:方法.flickr.photos.search API方法搜尋圖片,將以詞典為格式.
傳遞到引數中的block簡單地轉換詞典結果為模型物件,使ViewModel更容易使用.
程式碼使用linq_select方法通過LingToObjectiveC新增陣列.提供了API的陣列傳送.
最後在RWTFlickrSearchViewModel.m裡更新搜尋訊號日誌結果:

執行,輸入一個搜尋字元後在console裡檢視訊號的結果日誌:

相關文章