基於ResponderChain的物件互動方式

weixin_34050427發表於2018-05-10

前言

傳統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,事件處理邏輯就會變得極為難以管理。

轉自:Casa Taloyum一種基於ResponderChain的物件互動方式

相關文章