概述
iOS開發(fā)中拴孤,View上的很多事件需要通過delegate回調委托到處理業(yè)務的地方坷澡。以回調到ViewController為例豆挽,當View樹的深度較大的時候育谬,終端節(jié)點View上的事件需要往ViewController傳遞時,我們需要沿著View的層級一級一級定義delegate帮哈,顯得比較麻煩膛檀。聯(lián)想到iOS中事件的響應鏈模型時,隱隱覺得可以借助這個鏈達到傳遞事件的目的娘侍。果然咖刃,前段時間讀到的一篇blog就探討了這個問題,鏈接如下:https://casatwy.com/responder_chain_communication.html
實踐
該blog里已經描述了實踐方式憾筏,這里為了使用時的便利嚎杨,嘗試做一些改進:
- 傳參時減少使用dictionary,而使用array的方式
如果一個事件在傳遞的過程中氧腰,需要在鏈上的某些結點收集/添加數據枫浙,這時用dictionary傳參的方式挺有必要的。
但是更多的調用場景是古拴,在某個View上發(fā)生事件時箩帚,把業(yè)務參數都帶上,期望直接傳到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對象規(guī)避
如上所述,個人覺得以可變參數的方式調用最為簡單自然掂僵。示例如下:
[self routeEventWithName:@"EventSelectFundWithCode" wrappedValueParams:user.phone, user.name, nil];
這里有個問題航厚,user.phone參數如果為nil,則本次調用的參數經過va_list遍歷后都丟掉了锰蓬,這肯定違背了方法調用者的本意幔睬。
想了個解決方法來規(guī)避這個潛在的坑。我們規(guī)定芹扭,在調用時必須把參數都強制用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的方式處理業(yè)務
在業(yè)務處理的節(jié)點辅肾,示例代碼如下:
-(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的定義新娜。本文在實踐招式上的嘗試和優(yōu)化赵辕,權當作為一個參考。