iOS 響應式架構設計方案

2019-08-31

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

iOS-7-10-Emblem.jpg



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

2, 減少變量的(de)使用(yòng),由于它跟蹤狀态和(hé)值的(de)變化(huà),因此不需要再申明(míng)變量不斷地觀察狀态和(hé)更新值。

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

4, 當值随著(zhe)事件變換時(shí),可(kě)以使用(yòng)map,filter,reduce等函數便利地對(duì)值進行變換操作。

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

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

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

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

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

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

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

BeeHive 還(hái)爲組件設置了(le)優先級的(de)概念,它通(tōng)過數組來(lái)保持優先級排序,在源碼中能看到一些數組排序的(de)邏輯,這(zhè)就帶來(lái)了(le)相當多(duō)的(de)高(gāo)時(shí)間複雜(zá)度的(de)運算(suàn)。

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

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

在基于協議(yì)的(de)組件化(huà)方案中,組件使用(yòng)方能直接拿到目标組件的(de)實例,那麽使用(yòng)者可(kě)能對(duì)該實例進行修改,這(zhè)可(kě)能會帶來(lái)安全問題。
(3) 使用(yòng) Target-Action 解耦

Casa Taloyum 前輩的(de) iOS應用(yòng)架構談 組件化(huà)方案 爲此做(zuò)出了(le)最佳實踐。

Mediator 使用(yòng) Target-Action 來(lái)間接的(de)調用(yòng)目标組件,無需專門注冊。組件維護者需要做(zuò)一個(gè) Mediator 的(de)分(fēn)類,通(tōng)過硬編碼調用(yòng)目标組件,然後組件使用(yòng)者隻需要依賴這(zhè)個(gè)分(fēn)類就行了(le)。封裝的(de) Mediator 源碼隻有簡單的(de) 200+ 行代碼,并且很易懂(dǒng)。這(zhè)也(yě)讓開發者能對(duì)組件化(huà)的(de)實施更加有信心,不會因爲基礎設施的(de)錯誤而束手無策。
小總結

關于以上組件化(huà)的(de)簡單表述僅代表筆者的(de)個(gè)人(rén)見解,由于筆者并沒有真正的(de)實施組件化(huà),所以理(lǐ)解可(kě)能有誤。
雖然筆者設計的(de) iOS 架構不會應用(yòng)組件化(huà),但是這(zhè)給我們的(de)架構設計帶來(lái)了(le)前瞻性的(de)引導,這(zhè)非常重要。

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

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

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

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

大(dà)緻梳理(lǐ)了(le)一個(gè)文件分(fēn)類,當然這(zhè)個(gè)分(fēn)類是靈活的(de),隻是要分(fēn)模塊劃分(fēn):

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

具體裏面放了(le)些什(shén)麽,可(kě)以查看筆者的(de) DEMO。

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

    宏在預編譯階段替換爲實際代碼,存在效率問題
    使用(yòng)宏的(de)地方可(kě)能隻需要一塊内存,但是宏替換過後開辟了(le)多(duō)個(gè)(這(zhè)種情況應該用(yòng)常量替換宏)
    可(kě)能存在潛在的(de)宏命名沖突
    宏包裝過多(duō)的(de)代碼難以理(lǐ)解和(hé)調試
    代碼遷移時(shí)需要處理(lǐ)全局的(de)宏

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

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

或者用(yòng)常量來(lái)代替宏:

.h
FOUNDATION_EXTERN NSString * const kNotify_xxx;
//xxx通(tōng)知 key.m
NSString * const kNotify_xxx = @"kNotify_xxx";

這(zhè)麽做(zuò)也(yě)便于轉換思維,畢竟 swift 中是沒有宏的(de)。

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


去基類化(huà)設計;

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

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

類的(de)工具方法,按道理(lǐ)說可(kě)以提取爲模塊,但是有些場(chǎng)景可(kě)能顯得(de)不夠簡潔。

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

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

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

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

面向切面編程在 iOS 領域經典的(de)應用(yòng)就是利用(yòng) Runtime 去 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];
}

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

對(duì)于一些封裝的(de)組件,多(duō)考慮使用(yòng)協議(yì)來(lái)個(gè)性化(huà)定制,繼承作爲最差方案,而非是首選方案。

定義一個(gè)遵守組件定制協議(yì)的(de)屬性是常用(yòng)的(de)解決方法:

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

不同的(de)屬性作爲不同的(de)策略,組件内部通(tōng)過調用(yòng)對(duì)應的(de)協議(yì)方法實現個(gè)性化(huà)定制。而當使用(yòng)者想要改變策略時(shí),隻需要更改這(zhè)個(gè)屬性就行了(le)。面向協議(yì)設計模式結合策略模式是一個(gè)很好的(de)實踐。


MVC?MVP?MVVM?VIPER?;

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

實際上爲什(shén)麽要嚴格的(de)遵守架構模式呢(ne)?爲什(shén)麽每一個(gè)業務模塊的(de)架構模式都要一模一樣呢(ne)?

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

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

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

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

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

*請認真填寫需求,我們會在24小時(shí)内與您取得(de)聯系。