iOS 響應式架構設計方案

2019-08-31

 iOS 響應式架構設計方案
1.響應式編程是一(yī)種和事件流有關的編程模式,關注導緻狀态值改變的行爲事件,一(yī)系列事件組成了事件流。
2.一(yī)系列事件是導緻屬性值發生(shēng)變化的原因。FRP非常類似于設計模式裏的觀察者模式。
3.FRP與普通的函數式編程相似,但是每個函數可以接收一(yī)個輸入值的流,如果其中(zhōng),一(yī)個新的輸入值到達的話(huà),這個函數将根據最新的輸入值重新計算,并且産生(shēng)一(yī)個新的輸出。這是一(yī)種”數據流”編程模式。

iOS-7-10-Emblem.jpg



   iOS 響應式編程優勢;
1,開(kāi)發過程中(zhōng),狀态以及狀态之間依賴過多,RAC更加有效率地處理事件流,而無需顯式去(qù)管理狀态。在OO或者過程式編程中(zhōng),狀态變化是最難跟蹤,最頭痛的事。這個也是最重要的一(yī)點。

2, 減少變量的使用,由于它跟蹤狀态和值的變化,因此不需要再申明變量不斷地觀察狀态和更新值。

3, 提供統一(yī)的消息傳遞機制,将oc中(zhōng)的通知(zhī),action,KVO以及其它所有UIControl事件的變化都進行監控,當變化發生(shēng)時,就會傳遞事件和值。

4, 當值随着事件變換時,可以使用map,filter,reduce等函數便利地對值進行變換操作。

設計一(yī)個簡單的 iOS 響應式架構。iOS 架構 DEMO
關于組件化;
組件化似乎是項目發展壯大(dà)過後必然要做的事情,它能讓各個業務線的工(gōng)程師不需要過多的關注其他業務線的代碼,有效的提高團隊整體(tǐ)效率。然而實施組件化的時機是在需求相對穩定、産品閉環形成過後。所以本文不會應用組件化,但是這裏簡單談談業界的組件化方案。
組件化的核心問題就是組件間如何通訊。“軟件工(gōng)程的一(yī)切問題都能通過一(yī)個間接的中(zhōng)間層解決。”中(zhōng)介模式很自然的運用起來:
這樣雖然能統一(yī)組件間的通訊請求,但是卻沒有避免 Mediator 和目标組件的耦合,ModuleA 工(gōng)程中(zhōng)仍然需要導入 ModuleB

所以重點問題落在了解耦上:

要達到 Mediator 和目标組件的解耦,就需要實現它們之間的間接調用(圖中(zhōng)虛線),既然是間接調用,必然需要一(yī)種映射機制。在 iOS 開(kāi)發中(zhōng),業界大(dà)概有三種方式來處理。
(1) 使用 URL -> Block 解耦

簡單來說就是将組件的調用代碼放(fàng)入 block 中(zhōng),然後 URL 作爲 key,block 作爲 value,存入一(yī)個全局的 hash 容器,組件通過一(yī)個 URL (比如 “native/id=10/type=1” )向 Mediator 發起請求,Mediator 找到對應的代碼塊執行。由此,解開(kāi)了 Mediator 和目标組件的耦合(見博客:蘑菇街 App 的組件化之路)。

這種方案的缺陷很多:組件越多常駐内存越多;解析 URL 邏輯複雜(zá);URL 無法表述具體(tǐ)語言相關的對象類型。所以這種方式并不适合組件化解耦。
(2) 使用 Protocol 解耦

阿裏的 BeeHive 是該方案的很好實踐,筆者閱讀了一(yī)下(xià)源碼,它的大(dà)緻工(gōng)作原理如下(xià):注冊 Protocol 對應的組件,這個和上面說的 URL->Block 方式如出一(yī)轍,隻不過這裏是 Protocol-> Module ;組件申請訪問時導入對應的 Protocol 通過 Mediator 獲取到對應的組件對象。由于協議的表述能支持所有的對象類型,所以這種方式能基本解決組件間通信的需求。

BeeHive 注冊組件有幾種方式,一(yī)種是監聽(tīng)了動态鏈接時 image 二進制文件加載完成的回調,通過修改代碼段的方式判斷對應的模塊進行注冊;第二種是在 +load 方法裏面注冊;第三種是異步注冊,但是這種方式存在一(yī)個問題,可能組件使用方準備使用組件的時候,這個組件還未注冊成功。

BeeHive 還爲組件設置了優先級的概念,它通過數組來保持優先級排序,在源碼中(zhōng)能看到一(yī)些數組排序的邏輯,這就帶來了相當多的高時間複雜(zá)度的運算。

所以,組件數量過多的話(huà),會延長動态鏈接庫的過程。

BeeHive 爲了讓每一(yī)個組件享有獨自的 app 生(shēng)命周期、3D touch 等功能,會将這些系統級的事件發送給每一(yī)個組件,且不談大(dà)量的方法調用損耗,它必須讓入口文件 AppDelegate 繼承自 BeeHive 的 BHAppDelegate,筆者感覺侵入性過強,并且當開(kāi)發者需要複寫 AppDelegate 方法的時候,還要注意讓super調用一(yī)下(xià),可以說很不優雅了。

在基于協議的組件化方案中(zhōng),組件使用方能直接拿到目标組件的實例,那麽使用者可能對該實例進行修改,這可能會帶來安全問題。
(3) 使用 Target-Action 解耦

Casa Taloyum 前輩的 iOS應用架構談 組件化方案 爲此做出了最佳實踐。

Mediator 使用 Target-Action 來間接的調用目标組件,無需專門注冊。組件維護者需要做一(yī)個 Mediator 的分(fēn)類,通過硬編碼調用目标組件,然後組件使用者隻需要依賴這個分(fēn)類就行了。封裝的 Mediator 源碼隻有簡單的 200+ 行代碼,并且很易懂。這也讓開(kāi)發者能對組件化的實施更加有信心,不會因爲基礎設施的錯誤而束手無策。
小(xiǎo)總結

關于以上組件化的簡單表述僅代表筆者的個人見解,由于筆者并沒有真正的實施組件化,所以理解可能有誤。
雖然筆者設計的 iOS 架構不會應用組件化,但是這給我(wǒ)們的架構設計帶來了前瞻性的引導,這非常重要。

模塊化思維劃分(fēn)文件;

在團隊開(kāi)發中(zhōng),項目發展到後期總是會出現某些文件或代碼難以管理,出現這種情況的主要原因通常是項目開(kāi)發過程中(zhōng)對文件的管理過于随意。
開(kāi)發者應該盡量将所有代碼文件歸于模塊,而不要出現模拟兩可的文件。而筆者這裏說的模塊,是有具體(tǐ)意義的模塊,比如圖片處理模塊、字體(tǐ)處理模塊,而不是諸如 Public、Common 等無具體(tǐ)意義的代碼文件。

試想,在多人開(kāi)發中(zhōng),當所有人都覺得有些代碼不知(zhī)道怎麽歸類的時候,就會往 Public 裏面扔。當你某天想要整理一(yī)下(xià)這個 Public,會發現已經無從下(xià)手;或者當你需要遷移項目中(zhōng)的某個業務模塊時,會附帶遷移一(yī)些模塊,當這個模塊是有意義的(比如圖片處理模塊),你的遷移成本會非常低,但是當這個藕斷絲連的模塊是 Public 時,時間成本可能高于你的想象,估計你會将它完整的拷貝過去(qù),而又(yòu)對新項目造成了污染。

全局的公共文件是産生(shēng)垃圾代碼的源頭。筆者認爲幾乎所有的代碼都是可以歸類爲模塊的。

大(dà)緻梳理了一(yī)個文件分(fēn)類,當然這個分(fēn)類是靈活的,隻是要分(fēn)模塊劃分(fēn):

  - GeneralModules 放(fàng)項目獨有的通用配置模塊(比如通用顔色模塊、通用字體(tǐ)模塊) 
  - ToolModules 放(fàng)工(gōng)具類模塊(比如系統信息模塊)
  - PackageModules 放(fàng)基于業務的一(yī)些封裝(比如提示框模塊、加載菊花模塊)
  - BusinessModules 放(fàng)業務模塊(比如購物(wù)車(chē)、個人中(zhōng)心)

具體(tǐ)裏面放(fàng)了些什麽,可以查看筆者的 DEMO。

減少全局宏的使用;
很多時候,過多的宏讓項目很不整潔,每一(yī)個開(kāi)發者都往全局文件添加宏,而往往隻是一(yī)段簡單的代碼,筆者認爲開(kāi)發中(zhōng)應該盡量少使用宏,原因如下(xià):

    宏在預編譯階段替換爲實際代碼,存在效率問題
    使用宏的地方可能隻需要一(yī)塊内存,但是宏替換過後開(kāi)辟了多個(這種情況應該用常量替換宏)
    可能存在潛在的宏命名沖突
    宏包裝過多的代碼難以理解和調試
    代碼遷移時需要處理全局的宏

實際上,非得使用宏的地方并非那麽多,比如需要定義一(yī)個全局的導航欄字體(tǐ)方便使用,可以将通用字體(tǐ)的配置參數作爲一(yī)個模塊:

@interface HQGeneralFont : NSObject
/** 導航欄标題字體(tǐ) */
+ (UIFont *)navigationBarTitleFont;
@end

或者用常量來代替宏:

.h
FOUNDATION_EXTERN NSString * const kNotify_xxx;
//xxx通知(zhī) key.m
NSString * const kNotify_xxx = @"kNotify_xxx";

這麽做也便于轉換思維,畢竟 swift 中(zhōng)是沒有宏的。

iOS-8-on-iPhone-5s.jpg


去(qù)基類化設計;

代碼設計中(zhōng),應該盡量避免基類的使用,也就是說,你不應該總是要求開(kāi)發者去(qù)繼承你的基類來做功能。使用基類将造成不可避免的耦合,爲業務的長期發展帶來阻礙(當然某些情況是可以使用基類的)。

其實使用基類就算了,若是将大(dà)量的業務邏輯放(fàng)入基類中(zhōng)将是災難的開(kāi)端。試想,當項目新成員(yuán)一(yī)來就看見成千上萬行的基類代碼TA作何感想?
另外(wài)一(yī)種場景,當需要将項目中(zhōng)的某個模塊遷移到其他項目,或者需要将其他項目合并入當前項目,基類的合并将是一(yī)個非常頭疼的問題,它藕斷絲連的模塊和代碼會讓你抓狂。
那麽,類的工(gōng)具方法應該放(fàng)哪兒?對所有類的統一(yī)配置應該放(fàng)哪兒?對封裝模塊的個性化定制應該怎麽做?
裝飾模式

類的工(gōng)具方法,按道理說可以提取爲模塊,但是有些場景可能顯得不夠簡潔。

其實隻要留意 iOS 官方的 API,你就不難發現裝飾模式的大(dà)量應用,使用數個分(fēn)類将大(dà)量的方法按照功能分(fēn)類,會清晰且優雅:

@interface UIViewController (HQGeneral)
/** 基礎配置 */
- (void)HQGeneral_baseConfig;
@end

@interface UIViewController (HQGeneralBackItem)
/** 配置通用系統導航欄返回按鈕 */
- (void)HQGeneral_configBackItem;
/** 重寫該方法以自定義系統導航欄返回按鈕點擊事件 */
- (void)HQGeneral_clickBackItem:(UIBarButtonItem *)item;
@end

不過要注意的時,定義分(fēn)類的時候一(yī)定要加一(yī)個前綴标識以避免方法覆蓋。
AOP

面向切面編程在 iOS 領域經典的應用就是利用 Runtime 去(qù) Hook 方法:

+ (void)load {
    [self HQGeneralHook_exchangeImplementationsWithOriginSel:@selector(viewDidLoad) customSel:@selector(HQGeneralHook_viewDidLoad)];
}

+ (void)HQGeneralHook_exchangeImplementationsWithOriginSel:(SEL)originSel customSel:(SEL)customSel {
    Method origin = class_getInstanceMethod(self, originSel);
    Method custom = class_getInstanceMethod(self, customSel);
    if (origin && custom) {
        method_exchangeImplementations(origin, custom);
    }
}

- (void)HQGeneralHook_viewDidLoad {
    NSLog(@"進入:%@", self);
    [self HQGeneral_baseConfig];
    if (self.navigationController && [self.navigationController.viewControllers indexOfObject:self] != 0) {
        [self HQGeneral_configBackItem];
    }
   
    [self HQGeneralHook_viewDidLoad];
}

代碼中(zhōng)統一(yī)配置了 UIViewController 的系統導航欄返回按鈕,注意這裏調用的業務配置方法都是定義在 UIViewController 的分(fēn)類裏面的。若有某些導航欄需要格外(wài)配置返回按鈕的需求,可以拓展一(yī)個屬性來控制。
面向協議設計模式

對于一(yī)些封裝的組件,多考慮使用協議來個性化定制,繼承作爲最差方案,而非是首選方案。

定義一(yī)個遵守組件定制協議的屬性是常用的解決方法:

@property (nonatomic, strong) id<someprotocol>  strategy;</someprotocol>

不同的屬性作爲不同的策略,組件内部通過調用對應的協議方法實現個性化定制。而當使用者想要改變策略時,隻需要更改這個屬性就行了。面向協議設計模式結合策略模式是一(yī)個很好的實踐。


MVC?MVP?MVVM?VIPER?;

業務具體(tǐ)的架構模式是個讓很多開(kāi)發者頭疼的問題,因爲有時候能讓複雜(zá)業務更清晰,有時候卻因爲膠水代碼過多而臃腫。

實際上爲什麽要嚴格的遵守架構模式呢?爲什麽每一(yī)個業務模塊的架構模式都要一(yī)模一(yī)樣呢?

筆者認爲正确的架構思路一(yī)定是根據業務來的,不同的模塊,不同的業務線完全可以有不同的架構,隻需要架構足夠清晰不至于晦澀。

大(dà)緻設計了一(yī)下(xià)架構的主旋律:
    DataCenter 負責數據的獲取、處理、緩存等。
    Model 設計爲“瘦” Model,便于複用和遷移;也考慮到數據源可能數量龐大(dà),若 Model 設計得過于“胖”,會造成更多的内存占用。
    View 負責數據的展示,可以根據業務情況權衡是否需要 ViewModel 處理界面邏輯。
    ViewController 作爲 DataCenter 和 View 的橋梁。

筆者設計的項目目前不會很複雜(zá),多數情況上面的架構就已經夠用,若某個頁面功能過多,完全可以提取一(yī)些額外(wài)的模塊,比如 DataCenter 處理過于複雜(zá),那就把數據的處理和緩存提取出來:xxxDataProcesser、xxxDataCache。這些都是靈活的,隻需要按照模塊化的思維提取,ViewController 的代碼相信也不會太多。
關于響應式框架
Reactivecocoa 雖然強大(dà),筆者以前也用過,不過它是一(yī)個重量級框架,學習成本有點高,可能會因爲團隊成員(yuán)對其了解不足導緻難以定位的錯誤。
而美團的 EasyReact 似乎是一(yī)個福音,筆者大(dà)概浏覽了一(yī)下(xià)源碼,質量确實很高,對性能方面的處理很精緻,基于圖論算法的處理也感覺很棒,項目侵入性也很小(xiǎo)。不過缺點就是太新了,需要開(kāi)發社區一(yī)定時間的驗證,暫時筆者持觀望态度。

責任編輯:中(zhōng)山網站建設
 【網訊網絡】國家高新技術企業》十年專注軟件開(kāi)發,網站建設,網頁設計,APP開(kāi)發,小(xiǎo)程序,微信公衆号開(kāi)發,定制各類企業管理軟件(OA、CRM、ERP、訂單管理系統、進銷存管理軟件等)!服務熱線:0760-88610046、13924923903,http://www.wansion.net

您的項目需求咨詢熱線:0760-88610046(國家高新技術企業)

*請認真填寫需求,我(wǒ)們會在24小(xiǎo)時内與您取得聯系。