基於ResponderChain的物件互動方式
前言
傳統iOS的物件間互動模式就那麼幾種:直接property傳值、delegate、KVO、block、protocol、多型、Target-Action。但是有一天我在跟同事小龍結對程式設計的時候,他向我介紹了一個全新的互動方式:基於ResponderChain來實現物件間互動。
這種方式通過在UIResponder上掛一個category,使得事件和引數可以沿著responder chain逐步傳遞。
這相當於借用responder chain實現了一個自己的事件傳遞鏈。這在事件需要層層傳遞的時候特別好用,然而這種物件互動方式的有效場景僅限於在responder chain上的UIResponder物件上。
實現基於ResponderChain的物件互動
僅需要一個category就可以實現基於ResponderChain的物件互動。
.h檔案:
#import <UIKit/UIKit.h>
@interface UIResponder (Router)
- (void)routerEventWithName:(NSString *)eventName userInfo:(NSDictionary *)userInfo;
@end
.m檔案
#import "UIResponder+Router.h"
@implementation UIResponder (Router)
- (void)routerEventWithName:(NSString *)eventName userInfo:(NSDictionary *)userInfo
{
[[self nextResponder] routerEventWithName:eventName userInfo:userInfo];
}
@end
傳送事件時:
[self routerEventWithName:kBLGoodsDetailBottomBarEventTappedBuyButton userInfo:nil];
響應事件時:
#pragma mark - event response
- (void)routerEventWithName:(NSString *)eventName userInfo:(NSDictionary *)userInfo
{
/*
do things you want
*/
// 如果需要讓事件繼續往上傳遞,則呼叫下面的語句
// [super routerEventWithName:eventName userInfo:userInfo];
}
結合Strategy模式進行更好的事件處理
在上面的Demo中,如果事件來源有多個,那就無法避免需要if-else語句來針對具體事件作相應的處理。這種情況下,會導致if-else語句極多。所以,可以考慮採用strategy模式來消除if-else語句。
#pragma mark - event response
- (void)routerEventWithName:(NSString *)eventName userInfo:(NSDictionary *)userInfo
{
NSInvocation *invocation = self.eventStrategy[eventName];
[invocation setArgument:&userInfo atIndex:2];
[invocation invoke];
// 如果需要讓事件繼續往上傳遞,則呼叫下面的語句
// [super routerEventWithName:eventName userInfo:userInfo];
}
self.eventStrategy是一個字典,這個字典以eventName作key,對應的處理邏輯封裝成NSInvocation來做value。
- (NSDictionary <NSString *, NSInvocation *> *)eventStrategy
{
if (_eventStrategy == nil) {
_eventStrategy = @{
kBLGoodsDetailTicketEvent:[self createInvocationWithSelector:@selector(ticketEvent:)],
kBLGoodsDetailPromotionEvent:[self createInvocationWithSelector:@selector(promotionEvent:)],
kBLGoodsDetailScoreEvent:[self createInvocationWithSelector:@selector(scoreEvent:)],
kBLGoodsDetailTargetAddressEvent:[self createInvocationWithSelector:@selector(targetAddressEvent:)],
kBLGoodsDetailServiceEvent:[self createInvocationWithSelector:@selector(serviceEvent:)],
kBLGoodsDetailSKUSelectionEvent:[self createInvocationWithSelector:@selector(skuSelectionEvent:)],
};
}
return _eventStrategy;
}
在這種場合下使用Strategy模式,即可避免多事件處理場景下導致的冗長if-else語句。
結合Decorator模式
在事件層層向上傳遞的時候,每一層都可以往UserInfo這個dictionary中新增資料。那麼到了最終事件處理的時候,就能收集到各層綜合得到的資料,從而完成最終的事件處理。
#pragma mark - event response
- (void)routerEventWithName:(NSString *)eventName userInfo:(NSDictionary *)userInfo
{
NSMutableDictionary *decoratedUserInfo = [[NSMutableDictionary alloc] initWithDictionary:userInfo];
decoratedUserInfo[@"newParam"] = @"new param"; // 新增資料
[super routerEventWithName:eventName userInfo:decoratedUserInfo]; // 往上繼續傳遞
}
分析基於ReponderChain的物件互動方式
這種物件互動方式的缺點顯而易見,它只能對存在於Reponder Chain上的UIResponder物件起作用。
優點倒是也有蠻多:
以前靠delegate層層傳遞的方案,可以改為這種基於Responder Chain的方式來傳遞。在複雜UI層級的頁面中,這種方式可以避免無謂的delegate宣告。
由於眾多自定義事件都通過這種方式做了傳遞,就使得事件處理的邏輯得到歸攏。在這個方法裡面下斷點就能夠管理所有的事件處理。
使用Strategy模式優化之後,UIViewController/UIView的事件響應邏輯得到了很好的管理,響應邏輯不會零散到各處地方。
在此基礎上使用Decorator模式,能夠更加有序地收集、歸攏資料,降低了工程的維護成本。
基於ResponderChain的物件互動方式的適用場景首先要求事件的產生和處理的物件都必須在Responder Chain上,這一點前面已經說過,我就不再贅述了。
它的適用場景還有一個值得說的地方,就是它可以無視命名域的存在。如果採用傳統的delegate層層傳遞的方式,由於delegate需要protocol的宣告,因此就無法做到命名域隔離。但如果走Responder Chain,即使是另一個UI元件產生了事件,這個事件就可以被傳遞到其他元件的UI上。
舉個例子:XXXViewController屬於A元件,這個UIViewController的view上新增了B元件的某個YYView。那麼YYView的事件在通過Responder Chain被XXXViewController處理的時候,就可以不必依賴B元件的YYView了。當然,如果事件本身傳遞了只有B元件才有的物件,無視命名域這個優點就沒有了,不過這種場景在實際業務中其實也不多。
最後要說的是,由於事件被獨立了出來,它可以極大減輕MVC中C的負擔。在我們實際工程使用中,我建立了EventProxy物件,專門用於處理Responder Chain上傳遞的事件:
#pragma mark - event response
- (void)routerEventWithName:(NSString *)eventName userInfo:(NSDictionary *)userInfo
{
[self.eventProxy handleEvent:eventName userInfo:userInfo];
}
在這種場景下就做到了UI展示與事件處理的分離,事件處理的程式碼就可以歸攏到另外一個物件中去了,使得C的程式碼量得以減少。
或許可以算是一種新的架構模式:MVCE(Modle View Controller Event)?其實名字、模式什麼的都已經不重要了,畢竟所有的架構模式都是脫胎於MVC的,作不同程度的拆解罷了。
總結
這個互動方式是我的同事小龍告訴我的,我覺得很有意思。在實際工程中應用起來也十分得心應手,尤其是UI複雜且事件數量極多的場景,拿它來處理多事件邏輯是十分合適的。
我們在商品詳情頁中使用了這種物件互動方式:商品詳情頁有各種cell,每個cell上面又有各種button事件,每個Cell也有各自的子View,子View中也有button事件需要傳遞,而cell本身也需要相應點選事件。在這種複雜且多層級UI事件場景下,如果用delegate的方式層層傳遞,程式碼確實不如用Responder Chain的事件互動方式容易維護。用block的話,事件處理邏輯就會被分散在各個物件生成的地方。用Notification則更加不合適了,畢竟它並不屬於一對多的邏輯,如若其他業務工程師在其它地方也監聽了這個Notification,事件處理邏輯就會變得極為難以管理。
相關文章
- 基於 ResponderChain 的物件互動方式AI物件
- 研究:基於遙控器互動方式的電視遊戲機制設計原則遊戲
- web互動方式---ajaxWeb
- 基於layui的省市區三級聯動(資料互動)UI
- 基於php的教學互動網站系統PHP網站
- 元件化開發跨module互動方式—ModuleBus互動元件化
- 基於 HTML5 Canvas 的可互動旋鈕元件HTMLCanvas元件
- WKWebView和WebView與JS的互動方式WebViewJS
- 互動投影的幾種實現方式
- 邊做遊戲邊划水: 基於淺水方程的水面互動、河道互動模擬方法遊戲
- 基於聲網 Flutter SDK 實現互動直播Flutter
- C#與Python互動方式C#Python
- 基於 Agora SDK 實現 Windows 端的多人視訊互動(基於3.6.2版本)GoWindows
- 基於物件特徵的推薦物件特徵
- 基於 Agora SDK 實現 iOS 端的多人視訊互動GoiOS
- 以互動的方式升級ESXi主機
- Spring基於XML方式的使用SpringXML
- 基於Web實現遠端與硬體互動Web
- 基於Python實現互動式資料視覺化的工具(用於Web)Python視覺化Web
- Http(s)與後臺互動方式HTTP
- 基於 Canvas 的 HTML5 互動式地鐵線路圖CanvasHTML
- 基於HTML5Canvas的互動式地鐵線路圖HTMLCanvas
- B站直播間基於檢視互動的架構演進架構
- JS 基礎篇(一):建立物件的四種方式JS物件
- 基於動態圖互動網路的多意圖口語語言理解框架框架
- JavaScript物件與建立物件的方式JavaScript物件
- js和原生應用常用的資料互動方式JS
- GraphQL.js 與服務端互動的新方式JS服務端
- 基於 GDI 物件的 Windows 核心漏洞利用物件Windows
- Java獲取Class物件的方式和例項化物件的方式Java物件
- Spring中基於XML方式的AOP操作SpringXML
- 基於Maven建立SpringBoot的2種方式MavenSpring Boot
- Spring AOP基於xml的方式實現SpringXML
- 互動方式,Smartbi儀表盤還支援動態的資料展示
- Scapy 2.4.0 釋出,基於 Python 的互動式資料包處理庫Python
- 用Python基於Google Bard做一個互動式的聊天機器人PythonGo機器人
- 基於企業雲盤軟體的內外網檔案互動方案
- 雲物件 - 重新定義前後端互動物件後端