Since the list application scenario has a fixed process and mode, this paper first briefly introduces the structure of the list application scenario in QQ reading, and then proposes to template this scenario to improve the development efficiency and reduce the cost of communication and understanding. At the same time, a set of event handling scheme based on “Chain of Responsibility” is proposed, which also improves the development efficiency and reduces the trivial code amount to a certain extent.

This post is also published on my personal blog

Overview


Ten years have passed since 2009, the first year of mobile Internet development, and mobile client development technology has evolved from pioneer era to mature and stable stage. In a decade, tens of thousands of apps were created. However, if you look closely at the apps on the market, you’ll find that most of the apps and most of the scenarios are list-based. The so-called list application scenarios have the following characteristics:

  • Display multiple pieces of data in UITableView;
  • Data unchanged, used only for presentation, or with little state change;
  • No complex user interactions (e.g., UGC).

From the above characteristics, all list application scenarios have “the same code structure,” which means that we are often doing repetitive work. Also, “same code structure” means it can be templated. Template-based list application scenarios have the following benefits:

  • Improve work efficiency and reduce repetitive labor;
  • Unified code structure to reduce the cost of understanding and communication within the project team.

Brief introduction to the application scenario architecture of QQ Reading list


The flow of tabular application scenarios is similar to that of obtaining data from the network or local disk and presenting the data in the form of a Tableview. The main interaction is to click to enter the secondary page. Each project team probably has a set of architectures for list-like scenarios, and we have evolved a set of related architectures as WE have iterated over QQ Reading. This article will use this architecture as an example to describe the idea of templating.

It’s not about what architecture you use, it’s about how you template the architecture you use.

  • Manager(Interface) : corresponds to the Model “layer” in MVC, which is mainly responsible for data acquisition and management.
  • Controller: coordination hub of each module;
  • Cell/View: corresponding to the View in MVC, only responsible for UI layout logic;
  • ViewModel: Handles the business logic associated with UI presentation (see the previous article, “Customizing UI Component Libraries” for details);
  • Module(Interface) : Call it a “business Module.” A page consists of multiple modules of different or the same type. Such as QQ reading selection page “today must read”, “today second kill” are modules.

    Of course, Module can also be a simple style:

    In TableView each Module corresponds to a section. A Module parses network data, provides data for the TableView datasource (cell creation, etc.), and handles user events — all business logic for the Module (like the React Component).

The Main purpose of the Module is to lighten the burden of the Controller.

Typically, the Manager(Model) stores pure business data (data pulled from the network), which requires mapping between business data and Module “modules”. To avoid this layer of mapping, the Module parses and stores the business data directly. This approach also has disadvantages, because the network data parsing, control UI presentation logic (create cell, etc.) are placed in the Module. Makes Module violate the single responsibility principle.

As one of the five “SOLID” principles of object-oriented design, the Single Responsibility Principle (SRP) is easy to understand and difficult to grasp! “Just like the” right amount “in life, the right amount of salt, the right amount of water…” Uncle Bob, in Agile Software Development, describes the single responsibility principle for a class as “there should be only one cause for its change.”

In Module, network data parsing and UI presentation are two variable reasons — “The same UI is used to display the data returned by different network protocols, and the data returned by the same protocol is displayed as a different UI”. In QQ reading, the book list page belongs to “the same UI displays the data returned by different protocols” :

One of the tenets of “agile development” is to keep your code simple and refactor it when necessary to keep it from getting bad.

templated


The code for Controller, Manager, and API is basically fixed — it can be templated, and the ViewModel is highly reusable. Therefore, the main work of adding a list application scenario after templating is focused on Module.

Templating is to provide a set of code templates and replace the “Template” keyword with a business name during instantiation.

Our templates include: Manager, the Module, the View and API four directories, ZSTemplateManager. M (. H), ZSTemplateViewController m (. H) and ZSTemplateAPI m six files (.h), The main codes that can be templated are:

  • Controller: Set tableView (including dropdown, dropup, datasource, delegate), set navigation bar, error \ empty data processing, call to send request data to manager, event processing, etc.
  • Manager: Manage module, send network request to API, cache processing, etc.;
  • API: Sends network requests.

This means that the above functions can be generated by converting scripts with one click without having to write the code repeatedly. In particular, Controller can be used directly.

The conversion script and demo have been submitted to github “ZSTemplatedListScene”. The transformation script replaces the “Template” keyword in the Template with the business name (both in the code and in the file name). For example, the directory service in Demo:

Note: The template in demo is only a “Demo”, in which the network request, cache and other functions can be replaced by a unified module in the project.

In short, the conversion script can generate part of the code with one click, improving development efficiency. At the same time, the template also standardized the code structure, reducing the project team communication and understanding costs.

Incident handling scheme based on Chain of Responsibility


There are two pain points in the current event handling, so there is an event handling scheme based on Chain of Responsibility.

  • (1

    The hierarchy of views in most scenarios is shown in the figure above. As we know, a View does not normally handle user events and needs to be passed to the Controller level by level, so we need to pass the delegate level by level to handle events along the hierarchy shown above. The tedious repetition of trivial code has an unpleasant feel:

cell.delegate = controller;
view.delegate =cell; ...Copy the code
  • Pain point 2 As version iteration, different types of cells/Views are likely to have different event processing interfaces, as shown in the figure below:

    This is a serious violation of the open-closed principle of object-oriented design — you need to change it here every time you add a cell type.

The first one in particular has always bothered me. Not long ago, I saw a quote in Design Patterns introducing the “Chain of Responsibility” model: “Using existing links works well when the links support the chain you need. It saves you from defining links. And it saves space.” The nextResponder in UIResponder is the “existing links”. The event of the topmost View can be smoothly transmitted to the ViewController through the nextResponder chain, thus eliminating the cascade transmission of the delegate. Pain points 1 and 2 are then resolved. To do this, we add a class for passing and handling events to UIResponder:

@protocol ZSCEvent <NSObject>
@property (nonatomic.strong) __kindof UIResponder *sender;
@property (nonatomic.strong) NSIndexPath *indexPath;
@property (nonatomic.strong) NSMutableDictionary *userInfo;
@end

@interface UIResponder (ZSCEvent)
- (void)respondEvent:(NSObject<ZSCEvent> *)event;
@end

@implementation UIResponder (ZSCEvent)
- (void)respondEvent:(NSObject<ZSCEvent> *)event
{
    [self.nextResponder respondEvent:event];
}
@end
Copy the code

The UIResponder implementation simply passes the event to the nextResponder. Since the View does not contain business data, some information needs to be added as the event passes.

Therefore, we define ZSCEvent#userInfo to be mutable. Normally, exposed interfaces are immutable.

@implementation UITableViewCell (ZSCEvent)
- (void)respondEvent:(NSObject<ZSCEvent> *)event
{
    event.sender = self;
    [self.nextResponder respondEvent:event];
}
@end
Copy the code

For example, in the respondEvent: of the UITableViewCell you need to set the sender to self so that the corresponding Module can be found in the UIViewController cell.

- (void)respondEvent:(NSObject<ZSCEvent> *)event
{
    NSAssert([event.sender isKindOfClass:UITableViewCell.class], @"event sender must be UITableViewCell");
    if(! [event.sender isKindOfClass:UITableViewCell.class]) {
        return;
    }
    
    NSIndexPath *indexPath = [_tableView indexPathForCell:event.sender];
    id<ZSModule> module = [self.manager moduleAtIndex:indexPath.section];
    
    event.sender = self;
    event.indexPath = indexPath;
    [event.userInfo setObject:_tableView
                       forKey:ZSCEventUserInfoKeys.tableView];
    
    [module handleEvent:event];
}
Copy the code

The event handling code in the View could look like this:

- (void)_clickedButton:(id)sender
{
    ZSCEvent *event = [[ZSCEvent alloc] init];
    event.sender = self;
    [event.userInfo setObject:@(YES) forKey:@"clickedButton"];
    
    [self respondEvent:event];
}
Copy the code

If multiple events need to be processed in a cell, they need to be distinguished in userInfo, as in line 5 above.

In summary, with UIResponder’s nextResponder response chain, you no longer have to pass a delegate across the view hierarchy, reducing trivial code and improving development efficiency. At the same time, it also standardized the event handling scheme.

summary


Improving development efficiency and standardizing code structure is always our goal. The text improves the development efficiency and standardizes the code structure to a certain extent by templating the tabulated application scenarios and processing user events through the “Chain of Responsibility” mechanism.