A lightweight event delivery tool between iOS views

Posted by jimdy on Mon, 21 Feb 2022 17:24:06 +0100

preface

A long time ago, I saw a summary article about the responder chain and event processing mechanism based on iOS. The comment said that simple UI event processing can be carried out through this mechanism. At that time, I didn't care much and didn't have a deep understanding. Later, when I summarized this piece of content, I wrote such a gadget.

Such as subview - > vcview - > vc. Usually, we pass the events in the subview to vc and process them in vc. Basically, it is delivered through notifications, agents, block s, etc. However, we often encounter multiple levels of views, and we often have to pass them up layer by layer.

Is there any simple way to save us from writing these pass through codes?

Basic knowledge review

The responder chain and event handling mechanism will not be discussed in detail here. Briefly review two.

  • The links of the responder chain are:
    The view closest to the user is passed to the system.
    initial view –> super view –> ..... –> view controller –> window –> Application –> AppDelegate

  • Transfer chain:

    It is transmitted by the system to the view nearest to the user.

    Foreground App – > window – > root view – >... – > View

principle

  1. On the responder chain, continuously judge whether the responder has a protocol for receiving event processing.
  2. In the View level of View, according to the transmission chain, constantly recursively judge whether there is a protocol to accept event processing.

Cooperate with three enumeration values (ignore event and continue delivery, process event and continue delivery, process event and end delivery)

realization

Define protocol
@protocol THUIEventBusProtocol <NSObject>

@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
Two core processing methods
  1. Send events upward according to the responder link (such as view to the vc where it is located)

    + (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 <THUIEventBusProtocol> *curResponder = (UIResponder <THUIEventBusProtocol> *)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 <THUIEventBusProtocol> *)curResponder.thNextResponder;
        }
    }
    
  2. Transfer downward according to the transfer chain (for example, vc transfers to all its subview s)

    + (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 <THUIEventBusProtocol> *responderVC = (UIViewController <THUIEventBusProtocol> *)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) {
               // Sub vc
               [self passingEventsDownWithEventName:eventName responder:childVC data:data callback:callback];
            }
            
          	// Sub view
            [THUIEventBus handleViewWithEventName:eventName responder:responderVC.view data:data callback:callback];
        }
        
        // view
        if ([responder isKindOfClass:[UIView class]]) {
    				...
        }
    }
    

    How to handle the received events through eventName, ignore and continue to deliver? Process and continue delivery? Process and stop delivery?

    other

    Add a class extension to UIResponder and one more thNextResponder.

    You can actively set nextResponder to control the transmission of events.

    Method mapping table

    Such as the following scenario:

    In vc

    - (THUIEventResult)receivingUpwardPassedEventsWithEventName:(nonnull NSString *)eventName data:(nullable id)data callback:(void(^)(id _Nullable otherData))callback {
        return THUIEventResultHandleAndStop;
    }
    

    In this method, we accept the events passed from the sub view, and there may be different processing logic according to the eventName. At this point, you will first think of if else. If there are many events, consider writing a method mapping table.

    - (NSDictionary <NSString *, NSInvocation *> *)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;
    }
    

Project address

Topics: iOS objective-c