因 ResponderChain 的对象交互格局

首先感谢下 Tian Wei
Yu


平等种植基于ResponderChain的对象交互情势
这篇稿子,让自家懂对象中的竞相还有这种姿势。说实话,第一总体没有看通晓,自己随后敲了相同任何才亮,所以爆发矣登时篇稿子,算是个记录。

前言

Responder Chain
,也便是响应链,关于这点的知识为不是本文重点,还非极端懂的好错过看望就首作品:史书上无与伦比详细的iOS之波的传递及应机制-原理篇

于 iOS 中,对象中的并行情势大体有立几乎种植:间接 property
传值、delegate、KVO、block、protocol、多态、Target-Action
等等,本文介绍的是平等栽基于 UIResponder 对象交互模式,一句话来说,就是
通过在 UIResponder上挂一个 category,使得事件及参数能够本着 responder
chain 逐步传递。对于这种 subviews
特别多,事件同时需要层层传递的层级视图特别好用,不过,缺点也特别显明,必须依让
UIResponder 对象。

切切实实事例

我们先来探望下边这种异常广阔的界面:

简言之讲解下:最外层是个 UITableView,我们虽称为 SuperTable,每个 cell
里面又嵌套了单 UITableView,叫做 SubTable,然后是 SubTable 的 cell
里面来有按钮,大家理一下此界面的层级:

UIViewController -> SuperTable -> SuperCell -> SubTable ->
SubCell -> UIButton

设我们要以无比外层的 UIViewController
里捕获到这个按钮的点击事件,比如点击按钮需要刷新
SuperTable,那时候该怎么落实吗?

办法来成千上万,最广的便是 delegate
,然则盖层级太老,导致我们用平等百年不遇的失去落实,各样 protocol、delegate
表明,很麻烦,这种时刻,基于 Responder Chain 就老大便宜了。

切切实实使用

只是待一个 UIResponder 的 category 就执行:

@interface UIResponder (Router)

- (void)routerEventWithSelectorName:(NSString *)selectorName
                     object:(id)object
                   userInfo:(NSDictionary *)userInfo;


@end

@implementation UIResponder (Router)

- (void)routerEventWithSelectorName:(NSString *)selectorName
                             object:(id)object
                           userInfo:(NSDictionary *)userInfo {

    [[self nextResponder] routerEventWithSelectorName:selectorName
                                       object:object
                                     userInfo:userInfo];

}

@end

最好里层 UIButton 的点击处理:

- (IBAction)btnClick1:(UIButton *)sender {

    [self routerEventWithSelectorName:@"btnClick1:userInfo:" object:sender userInfo:@{@"key":@"蓝色按钮"}];

}

外层 UIViewController 的接收:

- (void)routerEventWithSelectorName:(NSString *)selectorName
                     object:(id)object
                   userInfo:(NSDictionary *)userInfo {

    SEL action = NSSelectorFromString(selectorName);

    NSMutableArray *arr = [NSMutableArray array];
    if(object) {[arr addObject:object];};
    if(userInfo) {[arr addObject:userInfo];};

    [self performSelector:action withObjects:arr];

}

事件响应:

- (void)btnClick1:(UIButton *)btn userInfo:(NSDictionary *)userInfo {

    NSLog(@"%@  %@",btn,userInfo);

}

倘若想在传递过程中新增参数,比如想以 SuperCell
那同一层加点参数,只待以对应之地点落实模式就是尽:

- (void)routerEventWithSelectorName:(NSString *)selectorName object:(id)object userInfo:(NSDictionary *)userInfo {

    NSMutableDictionary *mDict = [userInfo mutableCopy];
    mDict[@"test"] = @"测试";

    [super routerEventWithSelectorName:selectorName object:object userInfo:[mDict copy]];
}

计划思路

- (void)routerEventWithSelectorName:(NSString *)selectorName
                     object:(id)object
                   userInfo:(NSDictionary *)userInfo

细心之好窥见,我那边从来把 SEL 设计改为因为 NSString
的样式传递了,再以外头通过 NSSelectorFromString(selectorName)
转成对应的
SEL。原文中传的凡独用来标识具体是哪个事件之字串,还需要保障专门的
NSDictionary 来找到呼应的波,我看最好难为,不过补是
@selector(....)
声明与落实以一个地方,可读性强,也不便于出现拼写错误,导致触发不了对应措施的题目,具体怎么设计,大家不同吧~

有关参数的传递,比如我触发 UITableViewDelegate 中的
didSelectRowAtIndexPath: 方法,<2 个参数的情形,performSelector:
方法呢足以满意,但如 >2 个参数的言辞,就分外了,这时候我们就算可以用
NSInvocation 来实现,我形容了只分类,襄助传递多独参数,搭配以大方便:

@interface NSObject (PerformSelector)

- (id)performSelector:(SEL)aSelector withObjects:(NSArray <id> *)objects;

@end

@implementation NSObject (PerformSelector)

- (id)performSelector:(SEL)aSelector
          withObjects:(NSArray <id> *)objects {

    //创建签名对象
    NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:aSelector];

    //判断传入的方法是否存在
    if (!signature) { //不存在
        //抛出异常
        NSString *info = [NSString stringWithFormat:@"-[%@ %@]:unrecognized selector sent to instance",[self class],NSStringFromSelector(aSelector)];
        @throw [[NSException alloc] initWithName:@"ifelseboyxx remind:" reason:info userInfo:nil];
        return nil;
    }

    //创建 NSInvocation 对象
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];

    //保存方法所属的对象
    invocation.target = self;
    invocation.selector = aSelector;


    //设置参数
    //存在默认的 _cmd、target 两个参数,需剔除
    NSInteger arguments = signature.numberOfArguments - 2;

    //谁少就遍历谁,防止数组越界
    NSUInteger objectsCount = objects.count;
    NSInteger count = MIN(arguments, objectsCount);
    for (int i = 0; i < count; i++) {
        id obj = objects[i];
        //处理参数是 NULL 类型的情况
        if ([obj isKindOfClass:[NSNull class]]) {obj = nil;}
        [invocation setArgument:&obj atIndex:i+2];
    }

    //调用
    [invocation invoke];

    //获取返回值
    id res = nil;
    //判断当前方法是否有返回值
    if (signature.methodReturnLength != 0) {
        [invocation getReturnValue:&res];
    }
    return res;
}

@end

末尾附上
Demo