实现基于ResponderChain的对象交互

仅需要一个category就可以实现基于ResponderChain的对象交互。

.h文件:

#import <UIKit/UIKit.h>

@interface UIResponder (Router)

- (void)routerEventWithName:(NSString *)eventName userInfo:(NSDictionary *)userInfo;

.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,事件处理逻辑就会变得极为难以管理。

所以我写了这篇文章介绍了一下这个方式,希望能够在大家日常开发遇到类似场景时提供一点儿帮助。

我转载这篇文章用于学习。
文章来源 CasaTaloyum

这篇文件讲解:响应链/传递链

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容