以Responder Chain的方式传递View事件


概述

iOS开发中,View上的很多事件需要通过delegate回调委托到处理业务的地方。以回调到ViewController为例,当View树的深度较大的时候,终端节点View上的事件需要往ViewController传递时,我们需要沿着View的层级一级一级定义delegate,显得比较麻烦。联想到iOS中事件的响应链模型时,隐隐觉得可以借助这个链达到传递事件的目的。果然,前段时间读到的一篇blog就探讨了这个问题,链接如下:https://casatwy.com/responder_chain_communication.html

实践

该blog里已经描述了实践方式,这里为了使用时的便利,尝试做一些改进:

  1. 传参时减少使用dictionary,而使用array的方式

如果一个事件在传递的过程中,需要在链上的某些结点收集/添加数据,这时用dictionary传参的方式挺有必要的。
但是更多的调用场景是,在某个View上发生事件时,把业务参数都带上,期望直接传到ViewController里的回调方法里。而使用dictionary作为参数,需要定义每个key值字符串,很麻烦且容易出错。
我们可以直接按照ViewController里的方法参数顺序,把所有的参数装到一个数组里,再调用出去。代码如下:

UIResponder+Router.m:

- (void)routeEventWithName:(NSString *)eventName paramsList:(NSArray *)params {
        [[self nextResponder] routeEventWithName:eventName paramsList:params]; }

进一步,可以提供一个便捷方法,以可变参数列表的方式供调用,收集好参数到数组里,调用以上方法:

- (void)routeEventWithName:(NSString *)eventName wrappedValueParams:(NSValue *)firstWrappedParam, ... NS_REQUIRES_NIL_TERMINATION {
        NSMutableArray *argsArray = [[NSMutableArray alloc] init];
        va_list argList;
        if (firstWrappedParam) {
            [self addValueParam:firstWrappedParam toArray:argsArray];
            id arg;
            va_start(argList, firstWrappedParam);
            while (arg = va_arg(argList, id)) {
                [self addValueParam:arg toArray:argsArray];
            }
            va_end(argList);
        }
        [self routeEventWithName:eventName paramsList:argsArray];
    }
  1. 可变参数时的nil对象规避
    如上所述,个人觉得以可变参数的方式调用最为简单自然。示例如下:
    [self routeEventWithName:@"EventSelectFundWithCode" wrappedValueParams:user.phone, user.name, nil];

这里有个问题,user.phone参数如果为nil,则本次调用的参数经过va_list遍历后都丢掉了,这肯定违背了方法调用者的本意。
想了个解决方法来规避这个潜在的坑。我们规定,在调用时必须把参数都强制用NSValue包装一下(即便是nil值也可以),在va_list遍历时再把wrap的实际对象解出来。这时,调用示例变成这样了:

    [self routeEventWithName:@"EventSelectFundWithCode" wrappedValueParams:[NSValue valueWithNonretainedObject:user.phone], [NSValue valueWithNonretainedObject:user.name], nil];

解包的函数如下:

    -(void)addValueParam:(NSValue *)wrappedParam toArray:(NSMutableArray *)argsArray {
        if (![wrappedParam isKindOfClass:[NSValue class]] || [wrappedParam isKindOfClass:[NSNumber class]]) {
            DebugAssert(NO, @"Param should be wrapped by NSValue: %@", NSStringFromClass([wrappedParam class]));
        }
        id arg = [wrappedParam nonretainedObjectValue];
        arg = (arg != nil) ? arg : [NSNull null];
        [argsArray addObject:arg];
    }
  1. 以selector的方式处理业务
    在业务处理的节点,示例代码如下:
    -(void)routeEventWithName:(NSString *)eventName paramsList:(NSArray *)params {
        NSDictionary *eventSelectorDict = @{ kEventSelectLimit : NSStringFromSelector(@selector(didSelectLimit:)), kEventSelectMinBuyAmount : NSStringFromSelector(@selector(didSelectMinBuyAmount:))};
        [self performSelector:NSSelectorFromString(eventSelectorDict[eventName]) withParams:params];
    }

这里跟blog里不同的是,简单地以selector字符串作为策略dictionary的value,然后在performSelector里再转成selector。个人觉得最简单。
把performSelector的代码也贴上:
NSObject+PerformSelector.m:

    -(id)performSelector:(SEL)aSelector withParams:(NSArray *)params {
        NSInvocation *invocation = [self invocationWithSelector:aSelector];
        if (invocation == nil) {
            return nil;
        }
        NSInteger validArgumentsCount = MIN(invocation.methodSignature.numberOfArguments - 2, params.count);
        for (NSInteger i = 0; i < validArgumentsCount; i++) {
            id param = params[i];
            if ([param isKindOfClass:[NSNull class]]) {
                param = nil;
            }
            [invocation setArgument:¶m atIndex:i+2];
        }
        [invocation invoke];
        
        id result = nil;
        if (invocation.methodSignature.methodReturnLength != 0) {
            [invocation getReturnValue:&result];
        }
        
        return result;
    }

总结

用响应链来传递事件的思路相当新颖和巧妙。用该模式确实可以省掉不少delegate的定义。本文在实践招式上的尝试和优化,权当作为一个参考。

你可能感兴趣的:(以Responder Chain的方式传递View事件)