美文网首页程序员
基于 responder chain 的交互

基于 responder chain 的交互

作者: 申申申申申 | 来源:发表于2018-09-20 13:29 被阅读259次
  • 目录
  • 前言
  • 推导
  • 实现
  • 适用场景
  • 栗子
  • 优化
  • 致谢

1. 前言

抱歉,最近事情有些烦,文章没有更新

这几天在着手重构公司产品的首页模块,真的是不想吐槽接手的这份代码,简直就是一坨粑粑,完全没有架构和设计模式可言,有些模块所有的logicviewaction基本全都写在C中,可以看下面截图

某个C

当然👆这个图,主要并不是意在代码行数,而是在于这个C保存状态的数量

这个纯属吐槽,等项目几个主要模块重构完成,单独写一篇对 “天下武功出少林” 的MVC的使用


2. 推导

基于MVCV的事件响应需要传递到C中,由C来进行处理(自我更新 或者 更改M),这种

那么我们先来观察下继承链~

我们知道VC的父类是UIResponder

  • UIViewController - UIResponder - NSObject

V中常规展示的来讲,无非就是view上或者cell上有各种btn,各种view,各种label等,排列组合,然后展示给用户,我们看下其各自的继承链

  • UIView - UIResponder - NSObject
  • UITableView - UIScrollView - UIView - UIResponder - NSObject
  • UITableViewCell - UIView - UIResponder - NSObject
  • UIButton - UIControl - UIView - UIResponder - NSObject
  • UILabel - UIView - UIResponder - NSObject
  • ……

我们会发现,其中有两个比较重要的类,首先是NSObject这个是顶级父类,实力毋庸置疑;其次就是UIResponder这个类和其一个关键属性nextResponder

#if UIKIT_DEFINE_AS_PROPERTIES
@property(nonatomic, readonly, nullable) UIResponder *nextResponder;
#else
- (nullable UIResponder*)nextResponder;
#endif

正是这个nextResponder,可以让我们在基于responder chain来寻找我们需要响应的C

权威解释

3. 实现

实现还是超简单~
仅仅需要一个分类即可~

  • 参数eventName:是当前响应事件的标识(eg,可以用来区分用户是点击了btn还是view),可以在C中用来区分响应来源
  • 参数userInfo:可以用来传递一些参数
#import <UIKit/UIKit.h>
@interface UIResponder (FFRouter)
- (void)ff_routerEventWithEventName:(NSString *)eventName userInfo:(NSDictionary *)userInfo;
@end

实现1:

#import "UIResponder+FFRouter.h"

@implementation UIResponder (FFRouter)
// 空
- (void)ff_routerEventWithEventName:(NSString *)eventName userInfo:(NSDictionary *)userInfo {

}
@end

是的,这里是在分类中实现了一个空方法
因为笔者会基于responder chain直接寻找目标C,在V中调用这个方法,在目标C中,实现即可

实现2:
当然也可这样写

#import "UIResponder+FFRouter.h"
@implementation UIResponder (FFRouter)
- (void)ff_routerEventWithEventName:(NSString *)eventName userInfo:(NSDictionary *)userInfo {
   [[self nextResponder] ff_routerEventWithEventName:eventName userInfo:userInfo];
}
@end

这种写法使用时候
V中直接调用

[self ff_routerEventWithEventName:FF_HomeLinkCellDidClickButton userInfo:@{@"ff_linkcell_buttontag" : [NSString stringWithFormat:@"%ld", (long)button.tag]}]

但是这种写法
如果需要在当前viewnextresponder中继续向上传递的话
则需要在当前viewnextresponder中调用

[super ff_routerEventWithEventName:eventName userInfo:userInfo]

综上,笔者在重构的时候选择的是实现1,其实无论是1还是2,只要直接找到目标responder,其实都没影响,不需要调用super


4. 适用场景

我们知道,在VC的交互中,V经常使用protocolblock或其他来将响应传递到C

此时V可以分为两种情况(以cell代理为例)

  • V的布局简单,使用一次代理即可(如下图,cell中的btn需要传递响应事件到C
  • V的布局复杂(如下图,cell中有自定义的VAVA中有VB,……,最终有一个btn,此时需要将btn的响应事件传递到C,当然可以使用代理一层层的向上传递,直到C,那么就需要写好层多代理(当然,这种情况也可以使用notify,但是并不建议~))

当然,本文介绍的方案,笔者觉得无论是情况1还是情况2都可以完美解决~


5. 栗子

  • 首先是👆的分类
  • 定义事件常量
UIKIT_EXTERN NSString * const FF_HomeBannerCellEventNameButtonClick;
NSString * const FF_HomeBannerCellEventNameButtonClick = @"ff_button_click";
  • 响应btn的时候
- (void)didClickedButton:(UIButton *)button {
    UIResponder *responder = self.nextResponder;
    while (![responder isKindOfClass:NSClassFromString(@"JHTHomeVC")]) {
        responder = responder.nextResponder;
    }
    if ([responder isKindOfClass:NSClassFromString(@"JHTHomeVC")]) {
        [responder ff_routerEventWithEventName:FF_HomeBannerCellEventNameButtonClick userInfo:@{@"ff_banner_index" : [NSString stringWithFormat:@"%ld", index]}];
    }   
}
  • 在目标C中,通过判断eventname来判断事件来源,userinfo中是传递的参数
- (void)ff_routerEventWithEventName:(NSString *)eventName userInfo:(NSDictionary *)userInfo {
   // do something
}

6. 优化

其实在C中的实现,是有问题的
如果在view中有多个触发点击的地方,或者C中的tableview中有多个不同类型的cell,每个cell都是使用responder chain来交互的话,那么C中的ff_routerEventWithEventName: userInfo:实现必定存在if...elseif... ...else,作为有洁癖的程序员,真是无法忍受

因此,使用NSInvocation进行如下优化~

那么问题又来了,什么是 NSInvocation?
NSInvocation是一种消息处理机制,具体的看下图

权威解释
其实在之前的关于 消息转发 - 完整转发 中有使用到~ 有兴趣的同学可以点进去see see

继续正文

  • 首先在C中创建一个字典
@property (nonatomic, strong) NSDictionary<NSString *, NSInvocation *> *invocationDic;
  • 懒加载的时候,根据eventname来存储对应的方法实现
- (NSDictionary<NSString *,NSInvocation *> *)invocationDic {
    if (!_invocationDic) {
        extern NSString * const FF_HomeBannerCellEventNameBannerClick;
        extern NSString * const FF_HomeBannerCellEventNameButtonClick;
        extern NSString * const FF_HomeNoticeCellEventNameClick;
        extern NSString * const FF_HomeBdCellDidSelected;
        extern NSString * const FF_HomeLinkCellDidClickButton;

        _invocationDic = @{ FF_HomeBannerCellEventNameBannerClick : [self ff_creatInvocationWithSelector:@selector(ff_actionOfBannerCellOfBannerClick:)],
                           FF_HomeBannerCellEventNameButtonClick : [self ff_creatInvocationWithSelector:@selector(ff_actionOfBannerCellOfButtonClick:)],
                           FF_HomeNoticeCellEventNameClick : [self ff_creatInvocationWithSelector:@selector(ff_actionOfNoticeCellOfClick:)],
                           FF_HomeBdCellDidSelected : [self ff_creatInvocationWithSelector:@selector(ff_actionOfBdCellSelected:)],
                           FF_HomeLinkCellDidClickButton : [self ff_creatInvocationWithSelector:@selector(ff_actionOfLinkCellSelected:)]
                           };
    }
    return _invocationDic;
}
  • ff_creatInvocationWithSelector:的实现(这个方法,笔者是抽离到一个分类中,可供整个项目使用)
- (NSInvocation *)ff_creatInvocationWithSelector:(SEL)selector {
    NSMethodSignature *signature = [self.class instanceMethodSignatureForSelector:selector];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    invocation.target = self;
    invocation.selector = selector;
    return invocation;
}
  • 那么responder chainC调用即可如下
    那么为什么需要把userinfo的引用放在2位置呐?
    因为方法调用的时候,有两个默认参数,0 -> id self1 -> SEL _cmd
    即只可以在2位置插入我们的参数
- (void)ff_routerEventWithEventName:(NSString *)eventName userInfo:(NSDictionary *)userInfo {
    NSInvocation *invocation = self.invocationDic[eventName];
    [invocation setArgument:&userInfo atIndex:2];
    [invocation invoke];
}

最后看下重构后的首页吧~

优化后,没有了if...elseif... ...else,而且使用这种交互,事件响应逻辑得到很好的比较好的管理
至于代码好看不好看,其实都是看自己的编程风格而已啦~
反正我是有代码洁癖 哈哈~


啰嗦了这么多
最后还是非常感谢看到这里的同学,笔者感觉到非常开心~

困死了,眯一会去~


不定期更新 不合适的地方 还请指点~ 感激不尽

相关文章

  • 基于 responder chain 的交互

    目录 前言 推导 实现 适用场景 栗子 优化 致谢 1. 前言 抱歉,最近事情有些烦,文章没有更新 这几天在着手重...

  • iOS 响应者链

    概述 iOS 响应者链(Responder Chain) 是支持App界面交互的重要基础, 点击, 滑动, 旋转,...

  • 基于ResponderChain的事件传递

    ResponderChain对象交互方式本质 响应者链简介 Responder Chain也就是响应链,响应者链是...

  • ios的事件处理 一 基本概念

    Key word Event Handling Responder the Responder Chain Res...

  • iOS 响应者链

    一、概述 iOS 响应者链(Responder Chain)是支撑 App 界面交互的重要基础,点击、滑动、旋转、...

  • iOS响应者链彻底掌握2019-04-22

    概述 iOS响应者链(Responder Chain)是支撑App界面交互的重要基础,点击、滑动、旋转、摇晃等都离...

  • responder chain

    stack overflow上的有人提出responder chain问题UITableViewCell skip...

  • iOS 响应者链 整理

    一、响应者链(Responder Chain) 先来说说响应者对象(Responder Object),顾名思义,...

  • UIResponder的妙用

    转发:https://casatwy.com/responder_chain_communication.html...

  • 响应链(四)

    Altering the Responder Chain 变更响应链 You can alter the resp...

网友评论

    本文标题:基于 responder chain 的交互

    本文链接:https://www.haomeiwen.com/subject/spzinftx.html