很早前,看到过一个关于基于 iOS 响应者链条和事件处理机制的总结性文章,评论里有说可以通过这个机制进行简单的 UI 间事件处理。当时没太在意,未深入了解。后来自己总结此块内容时,写了这么个小工具。
如 subview -> vcview -> vc。通常我们是把 subview 中的事件传递到 vc,在 vc 中处理。基本的通过通知、代理、block 等进行传递。但是往往会遇到多 view 层级,往往我们得一层一层的向上传递。
有没有什么简单的办法可以让我们省去写这些传递代码?
关于响应者链条和事件处理机制在此不展开细说。简单回顾两条。
响应者链的链路是:
由离用户最近的 view 向系统传递。
initial view –> super view –> ….. –> view controller –> window –> Application –> AppDelegate
传递链条:
由系统向离用户最近的 view 传递。
前台 App –> window –> root view –> …… –> view
配合三个枚举值(忽略事件并继续传递、处理事件并继续传递、处理事件并结束传递)
@protocol THUIEventBusProtocol
@optional
/// receiving upward-passed events with event name
/// @param eventName event name
/// @param data data
/// @param callback after handle callback
- (THUIEventResult)receivingUpwardPassedEventsWithEventName:(nonnull NSString *)eventName data:(nullable id)data callback:(void(^_Nullable)(id _Nullable otherData))callback;
/// receiving downward-passed events with event name
/// @param eventName event name
/// @param data data
/// @param callback after handle callback
- (THUIEventResult)receivingDownwardPassedEventsWithEventName:(nonnull NSString *)eventName data:(nullable id)data callback:(void(^_Nullable)(id _Nullable otherData))callback;
@end
按照响应者链路向上传递事件 (如 view 传递给其所在的 vc)
+ (void)passingEventsUpwardsWithEventName:(nonnull NSString *)eventName
responder:(nonnull UIResponder *)responder
data:(nullable id)data
callback:(void(^)(id _Nullable otherData))callback {
if (eventName.length < 1 || !responder || ![responder isKindOfClass:[UIResponder class]]) return;
UIResponder *curResponder = (UIResponder *)responder.thNextResponder;
while (curResponder && ![curResponder isKindOfClass:[UIWindow class]] && ![curResponder isKindOfClass:[UIApplication class]]) {
NSLog(@" cur responder is %@", NSStringFromClass([curResponder class]));
if ([curResponder conformsToProtocol:@protocol(THUIEventBusProtocol)]) {
if ([curResponder respondsToSelector:@selector(receivingUpwardPassedEventsWithEventName:data:callback:)]) {
THUIEventResult res = [curResponder receivingUpwardPassedEventsWithEventName:eventName
data:data
callback:callback];
if (res == THUIEventResultHandleAndStop) break;
}
}
curResponder = (UIResponder *)curResponder.thNextResponder;
}
}
按传递链条向下传递 (如 vc 传递给其所有的 subview)
+ (void)passingEventsDownWithEventName:(nonnull NSString *)eventName
responder:(nonnull UIResponder *)responder
data:(nullable id)data
callback:(void(^)(id _Nullable otherData))callback {
if (eventName.length < 1 || !responder || ![responder isKindOfClass:[UIResponder class]]) return;
NSLog(@" cur responder is %@", NSStringFromClass([responder class]));
THUIEventResult res = THUIEventResultIgnoreAndContinue;
if ([responder isKindOfClass:[UIViewController class]]) {
// vc
UIViewController *responderVC = (UIViewController *)responder;
if ([responderVC conformsToProtocol:@protocol(THUIEventBusProtocol)]
&& [responderVC respondsToSelector:@selector(receivingDownwardPassedEventsWithEventName:data:callback:)]) {
res = [responderVC receivingDownwardPassedEventsWithEventName:eventName data:data callback:callback];
if (res == THUIEventResultHandleAndStop) return;
}
for (UIViewController *childVC in responderVC.childViewControllers) {
// 子 vc
[self passingEventsDownWithEventName:eventName responder:childVC data:data callback:callback];
}
// 子 view
[THUIEventBus handleViewWithEventName:eventName responder:responderVC.view data:data callback:callback];
}
// view
if ([responder isKindOfClass:[UIView class]]) {
...
}
}
通过 eventName 来区分接收到的事件如何处理,忽略并继续传递?处理并继续传递?处理并停止传递?
可主动设置 nextResponder,控制事件的传递。
如以下场景:
在 vc 中
- (THUIEventResult)receivingUpwardPassedEventsWithEventName:(nonnull NSString *)eventName data:(nullable id)data callback:(void(^)(id _Nullable otherData))callback {
return THUIEventResultHandleAndStop;
}
我们在这个方法中接受子 view 传递过来的事件,依据 eventName 可能有不同的处理逻辑。此时首先会想到 if else。若事件较多,可考虑写一个方法映射表。
- (NSDictionary *)methodDict {
if (_methodDict == nil) {
_methodDict = @{
kUIEventA : [self createInvocationWithSelector:@selector(handleEventA:)],
kUIEventB : [self createInvocationWithSelector:@selector(handleEventB:)],
kUIEventC : [self createInvocationWithSelector:@selector(handleEventC:)]
};
}
return _methodDict;
}
- (NSInvocation *)createInvocationWithSelector:(SEL)selector {
NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:selector];
if (signature == nil) {
NSString *info = [NSString stringWithFormat:@"-[%@ %@] : unrecognized selector sent to instance", [self class],
NSStringFromSelector(selector)];
@throw [[NSException alloc] initWithName:@"No such method" reason:info userInfo:nil];
return nil;
}
NSInvocation *invo = [NSInvocation invocationWithMethodSignature:signature];
invo.target = self;
invo.selector = selector;
return invo;
}