前言
傳統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,事件處理邏輯就會變得極爲難以管理。
所以我寫了這篇文章介紹了一下這個方式,希望能夠在大家日常開發遇到類似場景時提供一點兒幫助。