程序员

基于 responder chain 的交互

2018-09-20  本文已影响259人  申申申申申

1. 前言

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

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

某个C

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

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


2. 推导

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

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

我们知道VC的父类是UIResponder

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

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

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

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

权威解释

3. 实现

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

#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代理为例)

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


5. 栗子

UIKIT_EXTERN NSString * const FF_HomeBannerCellEventNameButtonClick;
NSString * const FF_HomeBannerCellEventNameButtonClick = @"ff_button_click";
- (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]}];
    }   
}
- (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

继续正文

@property (nonatomic, strong) NSDictionary<NSString *, NSInvocation *> *invocationDic;
- (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;
}
- (NSInvocation *)ff_creatInvocationWithSelector:(SEL)selector {
    NSMethodSignature *signature = [self.class instanceMethodSignatureForSelector:selector];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    invocation.target = self;
    invocation.selector = selector;
    return invocation;
}
- (void)ff_routerEventWithEventName:(NSString *)eventName userInfo:(NSDictionary *)userInfo {
    NSInvocation *invocation = self.invocationDic[eventName];
    [invocation setArgument:&userInfo atIndex:2];
    [invocation invoke];
}

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

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


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

困死了,眯一会去~


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

上一篇 下一篇

猜你喜欢

热点阅读