iOS架構設計系列之解耦的嘗試之變異的MVVM

話說天下大勢,分久必合,合久必分。周末七國分爭,并入于秦。及秦滅之后,楚、漢分爭,又并入于漢。漢朝自高祖斬白蛇而起義,一統天下,后來光武中興,傳至獻帝,遂分為三國 ---- 《三國演義》

最近一段時間,在思考如何合理的架構一個可擴展性良好的界面編程方式。這一部分的成果做成了一個叫ElementKit的庫。目前功能在不斷的完善中。

關于iOS的架構,看多了MVVM,VIPER,MVC,MVP,MVCS....的介紹文章。真正實踐下來的估計也就是MVC和MVVM了。MVC是系統自帶的框架,自然用的多。而MVVM解決了Controller臃腫的問題,再加上神器RAC也被很多團隊接納,甚至有一段時間出現了很多的文章鼓吹該模式如何如何了得。而在設計或者采納一個架構的時候,我們到底在思考些什么?

設計各個類的職責和他們之間的關系。

用個比較晦澀的說法是:關鍵概念抽象與概念關系設計。打個蓋房子的比方吧,關鍵概念抽象就是搞到鋼筋混凝土磚瓦石塊的基礎材料,而概念關系設計就是如何組織這些基礎材料:鋼筋為骨,水泥為膠,添磚加瓦為墻面,然后一個房子的雛形就有了。其實,在我理解,『架構』設計也應當類似。從一個宏觀的層面進行分析和抽象,找到合適的概念,這是分解,而后再把這些概念通過各種關系操作搞到一起,這是合成。分分合合中滿足一定的質量約束,我們有了一個架構。

希望滿足的質量約束

提高復用度

懶是第一生產力

這句話依稀記得是之前的GM說的,印象頗深。因為,仔細想想,我們在做設計和編碼的時候,都有一個原始的沖動就是少寫點代碼。最好,產品經理需求變動之后,我們不改代碼就能夠滿足他們的需求。任他天翻地覆,我自以不變應萬變。而黃粱一夢驚破醒,現實如此骨干。往往做業務開發的時候,疲于奔命的改改改。其實,我們的內心也是抗拒這樣的,少該點多好。那怎么才能少改點?

一個比較直接的做法就是:拿來就用。就是我們一直在說的組合模式。假設輪子都已經造好,要造個車,就只需要考慮如何去構架這個車的骨架和去實施,而不用去重新發明輪子(當然,輪子質量約束不滿足需求的情況下是得重新發明的)。沒有過多的思維負擔和實施負荷,當然就省事了。我們擁有越多的輪子,我們就能越省事。復用啊復用,這個懶的入門寶典。

于是提高整個系統和系統內部組成單元(模塊,類,函數。。。)的復用性就是一個非常重要的質量約束。通常情況下,談及復用我們想到更多的是類和函數。我們之前寫過一個什么什么功能的函數了,現在拿來就用好了。其實,還有更高一個思維層次的概念:業務邏輯。我們希望業務邏輯中的代碼也是可以復用的。比如一個ViewController的渲染邏輯(把整個VC的樣式包括nav和view都渲染成紅色)。我們希望在寫一個VC的時候,能夠很方便的復用之前寫的這樣的業務邏輯,即使VC的根view已經發生了改變,不再是原來的類型。

足夠的擴展性

新的東西被引入是永恒不變的事情。可能是新的頁面,新的業務邏輯,新的功能....當新的需求的到達的時候,我們的架構是否能夠快速的接納他們,以優雅而且高效的方式來實現他們?而不是,每次新的功能來了,要么傷筋動骨的大動干戈;要么削足適履,湊合湊合得了。

我們希望我們的代碼能夠有序的生長。脈絡清晰,每一個新功能都能夠在原有的架構中找到自己合適的位置。于是架構的可擴展性也是要追求的一個質量約束。這不是簡單的向前兼容,向后兼容的問題。而是要能夠以最小的代價,承載更多的需求。

關于可擴展性,更喜歡用函數來比喻。

f(x) = x^2 x屬于R

簡單的函數關系,對于無論什么樣屬于R的自變量x,我們都能準確的得到因變量f(x)的值。而一個理想狀態是,如果我們的架構如一個函數一般,對于未來的變化都進行了合適的抽象,無論你輸入是什么,都有對應的輸出,那該多好。

其他屬性

前面所說的兩個屬性,更多的是從系統的角度來思考的。當然我們從不同的角度來思考,還會要求我們的架構能夠滿足不能的質量約束。從運行的角度,我們希望CPU、內存、磁盤利用率好,沒有卡頓,從用戶的角度思考我們希望界面流暢。饕餮一樣,我們總是貪得無厭的想讓這個架構完美。然而,現實很骨感。我們只能先從幾個入手了,而ElementKit主要的關注點就在于復用度和擴展性。

構建

基礎框架概述

既然想偷懶,就得先看看我們之前在界面編程上面做了些什么,有哪些地方還有空間能讓我們偷懶。我們寫界面,在MVC的模式下面,一般我們會先建個XXViewController。然后在里面把數據的獲取維護,對于界面樣式和展示的管理都做了。且等一下,我們做的貌似不止這些:還有界面的維護(layout布局),還有一些子界面的構建,還有各種動畫效果的處理,還有頁面之間的跳轉....

這些暫且都稱之為職責。提到職責,很容易想起那個叫做單一職責的模式。一個類處理一個職責。而我們目前的問題是我們要創造出這個類來,那就按照職責劃分吧。每個職責一個類,這樣就會有一個初始的架構。但是我們還是會發現,其實有些職責可以合并,比如layout布局和自己面維護,我們可以在一個子View中處理掉。繼續向上歸納,我們把所有的職責分成了兩類:

  1. 業務邏輯
  2. 界面

一個是里子,一個是面子。于是創建了EKElement基類,該類是所有處理業務邏輯類的基類。起名Elemement,元素之意,即是希望它能夠有化學變化的魅力,在組合中創造出新的東西。而界面則復用UIView的整套規則。

@interface EKElement : NSObject <EKActionHandler, EKUIResponseHandler>
{
    Class _viewClass;
}
- (id) createResponser;
@property (nonatomic, assign, readonly) int64_t compareIdentifier;
@property (nonatomic, weak, readonly) UIResponder* uiEventPool;
@end

可以看到EKElement的屬性方法和方法并不多。

- (id) createResponser;

用于獲取一個新的該元素可以處理的界面類實例(可以是UIViewController,也可以是UIView)。總之一切可以響應用戶輸入之物皆可以。這個也就是屬性變量uiEventPool。

@property (nonatomic, weak, readonly) UIResponder* uiEventPool;

希望EKElement來處理一些業務邏輯的東西,通過這個EKElement的抽象,把我們一般業務邏輯中可以復用的部分提取出來。省的下次還得再寫一遍。關于這個可以看一個EKElment的子類實現EKTableElement

我們用EKTableElement類實現了一個TableView常用的邏輯,主要是一些二維數據的維護職責。同事我們也對Cell做了類似的處理。

EKCellElement處理了一些cell會處理的常用業務邏輯。而每一種Cell都是一個獨特的EKCellElement的子類去處理他獨特的業務邏輯。具體可以通過下面的類圖大概了解。

通過上述的處理實際上我們將EKCellElement當成了一個獨立的邏輯單元,他可以獨立處理一種Cell所有的展示和交互邏輯。舉個使用的例子:

上圖中我們可以看到三種類型的Cell:

  1. 完善個人信息那個Cell,YHToastCellElement
  2. 中間大圖Cell, YHInfoMessageCellElement
  3. 下部多圖片Cell, YHInfoMessageCellElement

這些Cell中還有著豐富的交互,下面兩個Cell中:點擊查看圖片了,點擊頭像和昵稱跳轉了,點擊右邊評論跳轉聊天了...,而第一個Cell還有點擊后跳轉頁面并移除自身的交互...。

在以前的編程模式中,我們要把這些交互寫在臃腫的ViewController中。而現在則是寫在具體的Element中,每個Elmenent有足夠的上下文信息來出來這些交互。

而上述界面完成的時候,并沒有多少代碼,

上圖中灰色部分為框架類,里面完成了絕大多數的TableView的邏輯。而主要寫的就是兩種不同樣式和業務邏輯的Cell(界面)和其Element(業務邏輯)。

這個地方有意思的事情就來了,我們知道在iOS開發中絕大多數的界面都是由TableView組成的。而TableView里面承載的內容千變萬化,尤其是在以Feed流為主體交互的應用中,比如聊天頁面和信息展示頁面,里面的Cell千變萬化,交互也多。而基于目前的ElementKit的方式,增加一種Feed的樣式,只需要寫個Element就行了,在Cell的Element里面處理幾乎所有的業務邏輯,而不需要臃腫的寫在ViewController里面。而一旦寫好一批Cell的Element之后,如果來了一個新的頁面,我們完全可以使用已有的看一下已有的Element是否能夠使用(樣式可以復用就復用Cell,業務邏輯可以復用就復用Element,這里寫好一些常用的擴展EKInputExtention),如果可以篩選出來,放進TableElement里面就行了。就像堆積木一樣,一個頁面就可以堆出來了。我們所追求的復用性和擴展性在這里,有了非常不錯的體現。

交互處理

在MVVM的架構實踐中,很多團隊喜歡配合ReactiveCocoa (RAC) 使用。我思考過這個原因,為什么:下雨天,MVVM和RAC更配偶?MVVM相對于MVC其職責劃分更細,必然引入了更多的類(view-model),而任何架構都很難脫離Apple的MVC的基礎,這勢必延長整個數據的傳遞鏈。基于響應式思想的RAC,使用流式處理數據。這正好縮短了使用過程中,數據傳遞路徑。可以直接將界面事件綁定到VM上,從來極大的減少開發成本。其實引入RAC是最好用方式,但是通過觀察發現:在我們的APP中真正需要傳遞的更多的是事件,而這個事件可能被Element相關聯的鏈條上的每一個元素響應,設計模式上類似于響應鏈模式。于是本著自己造輪子的作孽心態,寫了一套類似于CPU中央總線機制的EventBus.

當界面上一個事件發生的時候,會將該事件跑到中央總線上,所有掛在了總線上的實例都可以接口到該消息。按照責任鏈的方式傳遞事件,并響應事件。

總結

至此,我們通過結構設計解決了基礎的復用性和可擴展性問題,并通過設計了EventBus來解決了在設計的結構上的類之間的事件傳遞的問題。這樣我們得到的一個異變的MVVM架構。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容