天天看點

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

  一.函數響應式程式設計(function reactive programming)

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

  二. reactivecocoa簡介

  先簡單的介紹一下什麼是reactivecocoa架構,然後通過執行個體好好的去搞一搞這個架構,最後就是如何在項目中使用了。關于reactivecocoa的了解一些部落格(見本篇部落格中的連結分享)中把reactivecocoa比喻成管道,reactivecocoa中的signal就是管道中的水流。使用reactivecocoa可以友善的在mvvm各層之間架起溝通的管道,便于每層之間的互動。現在在我們做的工程中已經在使用reactivecocoa架構了,用起來的感覺是非常爽的,好用!

  可以說reactivecocoa中核心是信号量機制,signal在reactivecocoa中發揮着強大的不可代替的作用,可謂是reactivecocoa的靈魂所。signal是可以攜帶一些對象和參數的,你可以擷取該對象并且可以對該信号量攜帶的值進行map, filter等常用操作,操作後的值會和該信号量進行綁定。先簡單的這麼一說,後邊的部分回詳細的介紹如何讓信号量發揮強大的作用。

  三. 在工程中引入reactivecocoa

    1.你可以使用github上的加入方式如下所示,本人感覺比較麻煩,就沒有使用,采用的第二種方法(cocoapods)。

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

    2.上面的步驟難免有些麻煩,是以用cocoapods更為便捷一些,profile檔案中的内容如下所示,我用的是2.5版本。3.0後就支援swift了,因為我沒有用swift寫東西,是以就用的是2.5版本,設定完profile檔案後,pod install即可。

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

    你可以pod search reactivecocoa看一下版本,選擇你需要的版本即可。

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

  四.使用reactivecocoa

  下方會通過一些簡單的執行個體來介紹一下信号量機制和一些常用的方法。

    1.引入相應的頭檔案

    在工程中引入下方的頭檔案(建議在pch檔案中引入)就可以使用我們的reactivecocoa架構了

    2. sequence和map

    sequence:隊列,是reactivecocoa中引入的一個類型,它類似于數組,我們可以暫且把sequence看做綁定信号量的數組吧。在oc中的nsarray可以通過rac_sequence方法轉換成reactivecocoa中的sequence,然後就可以調用處理信号的一些方法了。

    參考以下執行個體代碼:

      (1)把nsarray通過rac_sequence方法生成rac中的sequence

      (2)擷取該sequence對象的信号量

      (3)調用signal的map方法,使每個元素的首字母大寫

      (4)通過subscribnext方法對其進行周遊輸出

    下方截圖是上個這個方法中的運作結果,從運作結果不難看出,通過signal相應的方法處理完後,處理的結果會與新傳回的信号量所綁定。原信号量中的值保持不變。每次信号量調用相應的方法處理完資料後,都會傳回一個新的信号量,而這個信号量是獨立于原信号量的。

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

    

    由上面的介紹可知,上面方法中的一坨代碼可以寫成下方的一串。因為一個方法調用後會傳回一個持有新結果的新的信号量,然後在這個信号量的基礎上再次調用信号量其他的方法。signal還有其他一些好用的方法,用法和map方法類似,在此就不一一贅述了,github上有相應的執行個體文檔。

    3.信号量開關(switch)

    上面把信号量比喻成水管,那麼switch就是水龍頭呢。通過switch我們可以控制那個信号量起作用,并且可以在信号量之間進行切換。也可以這麼了解,把switch看成另一段水管,switch對接那個水管,就流那個水管的水,這樣比喻應該更為貼切一些。下方是一個關于switch的一個小執行個體。

      (1) 首先建立3個自定義信号量(3個水管),前兩個水管是用來接通不同的水源的(google, baidu), 而最後一個信号量是用來對接不同水源水管的水管(signalofsignal)。signalofsignal接baidu水管上,他就流baidu水源的水,接google水管上就流google水源的水。

      (2) 把signalofsignal信号量通過switchtolatest方法加工成開關信号量。

      (3) 緊接着是對通過開關資料進行處理。

      (4) 開關對接baidu信号量,然後baidu和google信号量同時往水管裡灌入資料,那麼起作用的是baidu信号量。

      (5) 開關對接google信号量,google和baidu信号量發送資料,則google信号量輸出到signalofsignal中

    上面代碼輸出結果如下:

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

    4.信号量的合并

    信号量的合并說白了就是把兩個水管中的水合成一個水管中的水。但這個合并有個限制,當兩個水管中都有水的時候才合并。如果一個水管中有水,另一個水管中沒有水,那麼有水的水管會等到無水的水管中來水了,在與這個水管中的水按規則進行合并。下面這個執行個體就是把兩個信号量進行合并。

    (1) 首先建立兩個自定義的信号量letters和numbers

    (2) 吧兩個信号量通過combinelatest函數進行合并,combinelatest說明要合并信号量中最後發送的值

    (3) reduce塊中是合并規則:把numbers中的值拼接到letters信号量中的值後邊。

    (4) 經過上面的步驟就是建立所需的相關信号量,也就是相當于架好運輸的管道。接着我們就可以通過sendnext方法來往信号量中發送值了,也就是往管道中進行灌水。

   上面示例的運作輸出結果如下:

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

    下面是自己畫的原理圖,思路應該還算是清晰。

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

    5.信号的合并(merge)

      信号合并就了解起來就比較簡單了,merge信号量規則比較簡單,就是把多個信号量,放入數組中通過merge函數來合并數組中的所有信号量為一個。類比一下,合并後,無論哪個水管中有水都會在merge産生的水管中流出來的。下方是merge信号量的代碼:

      (1) 建立三個自定義信号量, 用于merge

      (2) 合并上面建立的3個信号量

      (3) 往信号裡灌入資料

    上面代碼運作結果如下:

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

    上面示例的原理圖如下:

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

  五. 在mvvm中引入ractivecocoa

    學以緻用,最後來個簡單的執行個體,來感受一下如何在mvvm中使用ractivecocoa。當然今天rac的應用是非常簡單的,但原理就是這樣的。接下啦我們要使用rac模拟一下登入功能,當然,網絡請求也是模拟的,這不是重點。重點在于如何在mvvm各層之間使用rac的信号量來更友善的在各個層之間進行響應式資料互動。下面這個執行個體的ui是非常簡單的,并且實作起來也是灰常簡單的,關鍵還是在于rac的應用。

    1.搭建demo所需ui,使用者界面非常簡單,公有兩個使用者界面,一個是登入頁面(兩個輸入框,一個登入按鈕),一個是登入後跳轉的頁面(一個展示使用者名和密碼的label)。下方是使用storyboard實作的使用者登入頁面。實作完後,個兩個頁面各自關聯一個viewcontorller類。

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

    2.下方是整個小demo的工程目錄,因為我們今天的重點是如何在mvvm中使用rac, 是以重點在于rac的應用,對于mvvm的分層就簡化一些。下方有vc層,在vc層中有兩個視圖控制器,一個是登入使用的視圖控制器(viewcontorller)另一個是登入成功後的視圖控制器(loginsuccessviewcontroller)。而viewmodel中則是負責登入的viewmodel業務邏輯層,該層中負責資料驗證,網絡請求,資料存儲等一些與ui無關的業務邏輯。

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

    3.實作登入的viewmodel層

    因為viewmodel層是獨立于ui層而存在的,是以可以在沒有ui的情況下我們就可以去實作相應子產品的viewmodel層。這正好減少了個個層次間的耦合性,同時也提高了可測試性,總體上改善了可維護性。好廢話少說,接下來要實作登入的viewmodel層。

    (1) 登入viewmodel層對應的類的頭檔案中的内容如下所示(vcviewmodel.h), 其實下方一些常用的信号量可以抽象出來放到viewmodel的父類中,這為了簡化demo沒有做父類的抽象。下方就是vcviewmodel中interface定義的公有屬性和公有方法(public)。username和password(nsstring類型) 用來綁定使用者輸入的使用者名和密碼。下方三個自定義信号量successobject, failureobject, errorobject 用來發送網絡請求的資料。successobject負責處理網絡請求成功且符合正常業務邏輯的事件, failureobject負責網絡請求成功不符合正常業務邏輯的處理,errorobject負責網絡異常處理。

   上面可能說的有些抽象,結合項目中的執行個體來解釋一下什麼時候發送successobject信号量,如何發送failureobject信号量,何時使用errorobject信号量。

   以某些理财app中購買理财産品的業務流程為例。在使用者下單之前先去判斷使用者是否實名認證以及綁定銀行卡,如果使用者已經實名和綁定銀行卡就走正常支付流程(使用者就是想去下單購買),vm就往vc發送successobject信号,目前vc就會根據信号量的訓示跳轉到下單支付頁面。  但是如果使用者沒有實名或者綁卡,那麼vm就給vc發送failureobject信号,根據信号量中的參數來判斷是走實名認證流程還是走綁定銀行卡流程。 errorobject就比較簡單了,網絡異常,背景伺服器抛出的異常等不需要ios這邊做業務邏輯處理的,就放在errorobject中負責錯誤資訊的展示。

    文字說完了,如果有些小夥伴還不太明白,那看下面這張原理圖吧。把三種信号量我們可以類比成十字路口的紅綠燈。successobject就是綠燈,可以走正常流程。failureobject是黃燈,先等一下,完成該做的就可以走綠燈了。而errorobject就是一紅燈,報錯異常,終止業務流程并提升錯誤資訊。有圖有真相,到這兒如果還不了解我就沒招了。

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

    在public方法中- (id) buttonisvalid; 負責傳回登入按鈕是否可用的信号量。- (void)login;發起網絡請求,調用登入網絡接口。

  

    (2)代碼的具體實作如下(vcviewmodel.m中的代碼),私有屬性如下。usernamesignal用來存儲使用者名的信号量,passwordsignal是用來存儲密碼的信号量。reqestdata則是用來存儲傳回資料的。

    (3)vcviewmodel的初始化方法如下,負責初始化屬性。

    (4) 發送登入按鈕是否可用信号的方法如下,主要用到了信号量的合并。

    (5) 模拟網絡請求的發送,并發出網絡請求成功的信号,具體代碼如下

  4. 上面是vm的實作,如果要進行單元測試的話,就對相應的vm類進行初始化,調用相應的函數進行單元測試即可。接着就是看如何在相應的vc子產品中使用vm。

    (1) 在vc中執行個體化相應的vm類,并綁定相應的參數和實作接收不同信号的方法,具體代碼如下:

    (2) 點選登入按鈕,調用vm中登入相應的網絡請求方法即可

    到此為止,一個完整模拟登入子產品的rac下的mvvm就實作完畢。當然上面的demo是非常簡陋的,還有好多地方需要進化。不過麻雀雖小,道理你懂得。主要是通過上面的demo來感受一下rac中的信号量機制以及應用場景。

    5.上面代碼寫完,我們就可以運作看一下運作效果了,下方是運作後的效果,

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

        viewmodel:

          kicking off network or database requests

          determining when information should be hidden or shown

          date and number formatting

          localization

        viewcontroller:

          layout

          animations

          device rotation 

          view and window transitions

          presenting loaded ui