美文网首页
以Responder Chain的方式传递View事件

以Responder Chain的方式传递View事件

作者: cheneyshan | 来源:发表于2018-01-30 18:58 被阅读0次

    概述

    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<id> *)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:&param atIndex:i+2];
            }
            [invocation invoke];
            
            id result = nil;
            if (invocation.methodSignature.methodReturnLength != 0) {
                [invocation getReturnValue:&result];
            }
            
            return result;
        }
    

    总结

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

    相关文章

      网友评论

          本文标题:以Responder Chain的方式传递View事件

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