概述
iOS开发中,View上的很多事件需要通过delegate回调委托到处理业务的地方。以回调到ViewController为例,当View树的深度较大的时候,终端节点View上的事件需要往ViewController传递时,我们需要沿着View的层级一级一级定义delegate,显得比较麻烦。联想到iOS中事件的响应链模型时,隐隐觉得可以借助这个链达到传递事件的目的。果然,前段时间读到的一篇blog就探讨了这个问题,链接如下:https://casatwy.com/responder_chain_communication.html
实践
该blog里已经描述了实践方式,这里为了使用时的便利,尝试做一些改进:
- 传参时减少使用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];
}
- 可变参数时的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];
}
- 以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:¶m atIndex:i+2];
}
[invocation invoke];
id result = nil;
if (invocation.methodSignature.methodReturnLength != 0) {
[invocation getReturnValue:&result];
}
return result;
}
总结
用响应链来传递事件的思路相当新颖和巧妙。用该模式确实可以省掉不少delegate的定义。本文在实践招式上的尝试和优化,权当作为一个参考。