preface

In iOS development, common MVC, the code in Controller of complex interface is extremely bloated, and thousands of lines of code are a disaster for later maintenance. So the MVC is also fun for Messive ViewController, especially there are many types of Cell TableView to exist, in – TableView: cellForRowAtIndexPath: proxy approach is filled with a lot of the if – else branch, This time we tried to implement the method “elegantly” in a new way.

There are only a few traditional interaction modes between objects in iOS: direct property transfer, Delegate, KVO, Block, Protocol, polymorphism, and target-Action. This time we’re going to look at ResponderChain for object interaction.

This allows events and parameters to be passed down the responder chain by hanging a category on the UIResponder.

This is equivalent to borrowing the Responder chain to implement its own event delivery chain. This is especially useful when events need to be passed through layers, but the only valid scenarios for this object interaction are UIResponder objects on the responder chain.

Second, MVVM separation logic, decoupling

There are many articles about MVVM on the Internet and everyone’s understanding may have a small difference. I will not repeat them here, but I will talk about the MVVM I used in the project. If there is any mistake, please see the officer for more advice. My tableView is defined in Controller, its proxy method is implemented in Contriller, and the ViewModel provides it with the necessary data:

In the header file:

#import <UIKit/UIKit.h>

@interface QFViewModel : NSObject

- (NSInteger)numberOfRowsInSection:(NSInteger)section;

- (id<QFModelProtocol>)tableView:(UITableView *)tableView itemForRowAtIndexPath:(NSIndexPath *)indexPath;

@end
Copy the code

There are two key methods in the implementation file:

- (NSInteger)numberOfRowsInSection:(NSInteger)section {
    return self.dataArray.count;
}

- (id<QFModelProtocol>)tableView:(UITableView *)tableView itemForRowAtIndexPath:(NSIndexPath *)indexPath {
    id<QFModelProtocol> model = self.dataArray[indexPath.row];
    return model;
}
Copy the code

Here, the Protocol Protocol is used for decoupling, two protocols, one view layer Protocol QFViewProtocol, one model Protocol QFModelProtocol

  • QFViewProtocol here provides a method to set the display of the model data interface
/** Protocol is used to store the data source setting method of each cell, or it can not be used to define the data source setting method of each type of cell directly. It is recommended to use */ @protocol QFViewProtocol <NSObject> /** to display @param Model model */ - through the model configuration cell (void)configCellDateByModel:(id<QFModelProtocol>)model;Copy the code
  • QFModelProtocol provides two properties, a reuse identifier and a row height
#import <UIKit/UIKit.h>/** protocol is used to store the reuse identifier and line height of each cell corresponding to model. */ @protocol QFModelProtocol <NSObject> - (NSString *)identifier; - (CGFloat)height; @endCopy the code

Create directly in the controller layer and addSubView:

- (void)initAppreaence {
    [self.tableView registerClass:[QFCellOne class] forCellReuseIdentifier:kCellOneIdentifier];
    [self.tableView registerClass:[QFCellTwo class] forCellReuseIdentifier:kCellTwoIdentifier];
    [self.view addSubview:self.tableView];
}
Copy the code

3. Pass click events based on ResponderChain

The iOS event-passing response has a response tree that can be used to eliminate header references in individual classes. To use this feature, you only need one method. Add a class to UIResponder and implement a method:

/** pass the event via the event response chain @param eventName eventName @param userInfo additional parameter */ - (void)routerEventWithName:(NSString *)eventName userInfo:(NSDictionary *)userInfo { [[self nextResponder] routerEventWithName:eventName userInfo:userInfo]; }Copy the code

When sending events, use:

[self routerEventWithName:kEventOneName userInfo:@{@"keyOne": @"valueOne"}];
Copy the code

Here, a dictionary is used to pass parameters, and decorator mode can be used, where each layer can add data to the UserInfo dictionary as the event layers are passed up. Then when it comes to the final event processing, the integrated data of all layers can be collected to complete the final event processing.

To pass this event all the way to the APPDelegate, call:

/ / the response chain continues to pass [super routerEventWithName: eventName the userInfo: the userInfo];Copy the code

Avoid if-else

In the book “Big Speech Design Mode”, the case of shopping mall discount is used to analyze the encapsulation of strategy mode for different algorithms. Here we use strategy Mode to encapsulate NSInvocation, which is used for method invocation and will be forwarded by NSInvocation at the last stage of message forwarding. Let’s look at the NSInvocation example with a method invocation

#pragma mark-Invocation invocation method
- (void)invocation {
    SEL myMethod = @selector(testInvocationWithString:number:);
    NSMethodSignature *sig = [[self class] instanceMethodSignatureForSelector:myMethod];
    NSInvocation * invocatin = [NSInvocation invocationWithMethodSignature:sig];
    
    [invocatin setTarget:self];
    [invocatin setSelector:myMethod];
    
    NSString *a = @"string";
    NSInteger b = 10;
    [invocatin setArgument:&a atIndex:2];
    [invocatin setArgument:&b atIndex:3];
    
    NSInteger res = 0;
    [invocatin invoke];
    [invocatin getReturnValue:&res];
    
    NSLog(@"%ld",(long)res);
}

- (NSInteger)testInvocationWithString:(NSString *)str number:(NSInteger)number {
    
    return str.length + number;
}
Copy the code
  • The first step is to generate the method signature through the method.
  • The second step is to set the parameters, notice here[invocatin setArgument:&a atIndex:2];Index starts at 2 instead of 0, because there are also two hidden arguments self and _cmd that occupy two of them
  • Third step[invocatin invoke];

The invocation key is the event name and the value is the corresponding Invocation object, which is called when the event occurs

- (NSDictionary <NSString *, NSInvocation *>*)strategyDictionary {
    if(! _eventStrategy) { _eventStrategy = @{ kEventOneName:[self createInvocationWithSelector:@selector(cellOneEventWithParamter:)], kEventTwoName:[self createInvocationWithSelector:@selector(cellTwoEventWithParamter:)] }; }return _eventStrategy;
}

Copy the code

Call the method that the UIResponder here – (NSInvocation *) createInvocationWithSelector: (SEL) generated by the selector invocation:

/** Generate the NSInvocation @param selector method @ via SELreturnInvocation object * / - (NSInvocation *) createInvocationWithSelector: (SEL) the selector {/ / by the method name NSMethodSignature creation method signature *signature = [[self class] instanceMethodSignatureForSelector:selector]; / / create the invocation NSInvocation * invocation = [NSInvocation invocationWithMethodSignature: signature]; [invocationsetTarget:self];
    [invocation setSelector:selector];
    return invocation;
}
Copy the code

5. Event handling

After the above steps, – (void)routerEventWithName:(NSString *)eventName userInfo:(NSDictionary *)userInfo To pass this event on:

#pragma mark - Event Response- (void)routerEventWithName:(NSString *)eventName userInfo:(NSDictionary *)userInfo {// handle the event [self handleEventWithName:eventName parameter:userInfo]; / / the response chain continues to pass [super routerEventWithName: eventName the userInfo: the userInfo]; } - (void)handleEventWithName:(NSString *)eventName parameter:(NSDictionary *)parameter {// get the NSInvocation object  *invocation = self.strategyDictionary[eventName]; // Set the Invocation parameters [InvocationsetArgument:&parameter atIndex:2]; // Invocation invoke; } - (void)cellOneEventWithParamter:(NSDictionary *)paramter { NSLog(@"First cell event --------- Parameter: %@",paramter);
    QFDetailViewController *viewController = [QFDetailViewController new];
    viewController.typeName = @"Cell Type 1";
    viewController.paramterDic = paramter;
    [self presentViewController:viewController animated:YES completion:nil];
}

- (void)cellTwoEventWithParamter:(NSDictionary *)paramter {
    NSLog(@"Second cell event --------- Parameter: %@",paramter);
    QFDetailViewController *viewController = [QFDetailViewController new];
    viewController.typeName = @"Cell Type 2";
    viewController.paramterDic = paramter;
    [self presentViewController:viewController animated:YES completion:nil];
}
Copy the code

Six, afterword.

At the end of this article, to sum up, there are still a lot of things that are worth going into:

  • The use of the Protocol
  • Event handling: Mechanisms for generating, delivering, and responding to events
  • Design patterns: Policy patterns, decorator patterns, and use of MVVM
  • NSInvocation usage and message forwarding mechanism

Any comments and suggestions on the Demo are welcome to exchange guidance. If possible, please give a STAR at your convenience.

Thank you for your comments, correct my tableView definition in VM subversive error!

Finally, many thanks to Casa for your sharing! Responderchain-based object interaction