preface

The concepts of componentization and Router may have been relatively new a few years ago, and I believe that most students are familiar with these terms. The author has read some articles about the ideas and frameworks of componentization and Router decoupling before he really came into contact with Router and used it in the project. However, due to my lack of practice, and failed to really apply it to the project practice. As a result, after reading the articles, the knowledge I had understood could not be truly transformed into skills that could solve problems. The author was fortunate to contact and use the famous open source library JLRouter in the project to solve the jump logic between all pages inside and outside the App. After years of study and use, I recorded it and solidified my knowledge. In addition, there are few complete and detailed Demo articles about the use of componentization Router on the Internet. The specific use of Router in the project still needs further research. Therefore, if there is anything wrong or need to be supplemented in this article, please kindly comment. This article is biased to actual combat, want to in-depth study Router idea recommended frost God written iOS componentization – route design analysis.

==Demo in the article most 👇==

Why does the Router

Every time I encounter a novel framework, I can’t help asking myself these questions. I hope the following brief overview will help you understand Router.

  • What is routing and what problem does it solve

    The above picture vividly shows the intricate relationship between each controller module in the project, which can be even worse if we do not handle it properly. Using the Router looks something like this;

    For example, Router is the same as the Router we use in daily life. Each controller in App can be imagined as a different device connected to the Router. Of course, when connecting to the Router, you generally need to enter the password. The Router saves the URL of each device. When devices need to interact with each other, the Router sends messages to the Router for unified processing. When controllers need to interact with each other, they only need to send the corresponding URL address to the Router. The Router addresses each other’s information according to its registered URL, and then is responsible for instantiating objects, transmitting parameters, and jumping. Each controller does not need to depend on each other.Perfect solution for coupling between different modules!

  • The Router can do a lot of things. First, we use it to solve the tricky controller coupling relationship. It is a very effective solution. In App, controller jumps are generally divided into three types: Modal jump (presented/dismiss), navigation controller jump (Push/ POP), Storyboard jump (Segue), UITabBarVC main controller Index switch; In addition to the normal controller to controller jump, there will be 3D Touch to specify a controller to jump to; Jump between apps: URL Scheme, Universal Links; It can be imagined that no matter in the App page switch, external call, will involve the controller jump, switch and so on; The following is a reference to a common scenario:

If the scenario of A Push B, B Modal C is implemented before the introduction of Router: the general practice is to introduce B in A, C in B, and then A hard coding is needed before each jump.

//A pushes B to page B and sets @perpeoty, callback, etc.;#import "B"
B* BVC = [B new]; 
BVC.delegate = A;
BVC.name = @"jersey"; BVC.callback = ^ (id data) { }; . . . Set some business parameters to B, delegate, callback, etc. [A.nav pushVC: BVC animation:true]; 
// B -> C 
#import "C"
C* CVC = [C new];
[B presentVC: CVC];
[B presentVC: CVC animation: true completion: nil];
Copy the code

== Pseudo code after Router :==

After referring to the Router, our code looks like this in the same scenario; ==JSDVCRouter == == == == == == == == == ==

    // A Push B;
    #import "JSDVCRouter"
    [JSDVCRouter openURL: BVCPath info: @{@"delegate":self,@"name": @"jersey"The @"callback":callback}]; // BVCPath: indicates the Path defined for controller B. It is generally stored in the global Map. Each Path maps to the current controller Map, including relevant parameters such as title, class, needLog, etc. // B Modal C [JSDVCRouter openURL: C info: {kJSDRouteSegue: @"Modal"}]; // The switch between controllers is implemented in Push by default. When Modal is needed, a parameter is passed;Copy the code

1. The Router is a Router that can be used with a Router. Coupling degree reduction: A controller does not need to know the existence of B controller, just need to import “JSDRouter”, by its corresponding jump logic, as well as the assignment and so on; 2. Improved code reading: Of course, at the beginning of contact, it looks not very strange, but after contact for a period of time, it not only reduces the number of lines of code, but also has high readability. Say goodbye to push/pop, present/dismiss. 3. Improve code reuse: each jump between the controller and assignment and other operations, need repeatable code a **(serious violation of the principle of: reusability)**, through JSDRouter will jump and assignment and other logic encapsulation, a code, lifetime use; 4. Easy maintenance: It is a little difficult to write this point. As the project grows with the scale of the company, the number of controllers and the jump become more and more complex, and the jump method and logic are easy to become more and more confused, which makes it difficult to manage in the later stage. JSDVCRouter is responsible for all jumps in the App, which can effectively improve testing and maintenance. Of course, the cost is to maintain the RouterMap and improve the internal logic of JSDVCRouter. 5. Dynamic and flexible: The Router can be used in conjunction with the background response to transfer the Key of the response to determine the real page to jump to, rather than hard-coded jump; 6. To be added:

  • How many steps are required to implement Router to complete controller hop? Switching a controller to a Router for the first time is very simple with only 3 steps. If the demand does not change much, it will be almost a permanent solution.
  1. Map table creation: It is a global Map. The corresponding controller in the App defines the Path. The Router can Map the Map based on the Path to the corresponding controller, which contains at least the parameters of the current controller, such as {@”Class”: @” controller Class name “}. It is equivalent to calling this route and getting a set of maps bound to it as parameters, and initializing the instance through Class. The code structure is as follows:
     + (NSDictionary *)configInfo
        return @{ JSDRouteHomeCenter: @{
                     @"class": @"JSDAHomeCenterVC"The @"name": @"Home page"The @"parameter": @""The @"needLogin": @"0", },
                  JSDRouteUserLogin: @{
                     @"class": @"JSDUserLoginVC"The @"name": @"Login"The @"parameter": @""The @"needLogin": @"0",}};Copy the code
  1. Packaging JLRouter; ** For easy use, management, and later migration, etc.! ** Similar to the use of AFNetwork, SDWebImage, MJRefresh and other well-known open source libraries, because the open source library provides very rich functions, but we may actually use only one or two of its main functions to solve problems existing in the project, Everyone will be based on the company’s specific business scenarios or usage habits, to carry out a layer or even multiple layers of encapsulation, so that it can be more suitable for the actual requirements; The author makes a layer of encapsulation + Category form: JSDVCRouter,JSDVCRouter + Add; JSDVCRouter: mainly used to declare the Router call interface; JSDVCRouter + Handle: it is mainly used to implement Router registration and process the jump between controllers and parameter assignment codes.
  2. [JSDVCRouter openURL:BVC] [JSDVCRouter openURL:BVC]

Maintenance during service change

  1. Map maintenance: With the development of services, when new pages are added to the Map, add a specified Path and corresponding binding parameters to the Map.
  2. JSDVCRouter maintenance: it contains the code that actually initializes the jump and assignment to the controller, which is rarely changed here; For example, in the later stage, we need to support the jump to H5, deal with 3D Touch, Universal Links, and come here for maintenance;

Actual Code!

I don’t know whether the brief introduction of realizing controller jump on Router mentioned above will help students who have initial contact with Router. I hope the following Code can make you better understand and use it! The following details the author package JLRoutes to achieve controller jump three classes:

JSDVCRouterConfig

This file is mainly used to manage the configuration files (title,needLogin, etc.) of all routers mapped to the specified controller class and related parameters. Specific configurations can be made based on actual project requirements.

  1. Extern NSString* const specifies the Router URL with an external NSString* const declaration to better check for errors at compile time. Extern NSString* const specifies the Router URL with an external NSString* const declaration.
  1. In this way, the Router URL management is easier to read and maintain. If you use @”/login” to bind the Router URL, it is easy to make careless errors.

The code is as follows:

// Extern NSString* const JSDVCRouteWebview; extern NSString* const JSDVCRouteLogin; @interface JSDVCRouterConfig : NSObject + (NSDictionary *)configMapInfo; @end //App related controller NSString* const JSDVCRouteWebview = @"/webView";
    NSString* const JSDVCRouteLogin = @"/login";
    @implementation JSDVCRouterConfig
    
    + (NSDictionary *)configMapInfo {
    
    return @{
        JSDVCRouteWebview: @{@"class": @"JSDWebViewVC"The @"title": @"WebView"The @"flags": @""The @"needLogin": @"",
        },
        JSDVCRouteLogin: @{@"class": @"JSDLoginVC"The @"title": @"Login"The @"flags": @""The @"needLogin": @"",}}; @endCopy the code

JSDVCRouter

The internal implementation of this class is very simple. It inherits from NSObject to register and call the Router interface externally, and calls the interface provided by JLRoutes internally.

All jumps in a project call the Router using the interface provided by this class; One takes no parameters by default and the other one takes the parameters we need (NSDictionary);

[JSDVCRouter openURL:JSDVCRouteAppear]; / / push to AppearVC; [JSDVCRouter openURL:JSDVCRouteAppear parameters:@{kJSDVCRouteSegue: kJSDVCRouteSegueModal, @"name": @"jersey"}]; // Modal to Appear VC with parameter name;Copy the code

Benefits of packaging a JSDVCRouter separately:

Prevent third-party library intrusion. It inherits from NSObject and does not directly depend on JLRouter, so that if you later consider replacing the tripartite library or encapsulating a set of functionality similar to that provided by JLRouter, you only need to modify it. Interface isolation is unified and more readable;

@interface JSDVCRouter : NSObject + (BOOL)openURL:(NSString *)url; / / call the Router; + (BOOL)openURL:(NSString *)url parameters:(NSDictionary *)parameters; + (void)addRoute:(NSString* )route handler:(BOOL (^)(NSDictionary *parameters))handlerBlock; // Register the Router. When calling the Router, the callback will be triggered. @end#define JSDRouterURL(string) [NSURL URLWithString:string]

    @implementation JSDVCRouter
    
    + (BOOL)openURL:(NSString *)url {
        
        return [self routeURL:url parameters:nil];
    }
    
    + (BOOL)openURL:(NSString *)url parameters:(NSDictionary *)parameters {
        
       return [self routeURL:url parameters:parameters];
    }
    
    + (void)addRoute:(NSString *)route handler:(BOOL (^)(NSDictionary * _Nonnull parameters))handlerBlock {
        
        [JLRoutes addRoute:route handler:handlerBlock];
    }

    #pragma mark - mark JLRouter
    
    + (BOOL)routeURL:(NSString*)url parameters:(NSDictionary *)parameters{
        
        return [JLRoutes routeURL:JSDRouterURL(url) withParameters:parameters];
    }
    
    @end
    
Copy the code

JSDVCRouter+Handle

The logic to handle the callback controller jump and parameter assignment when actually registering and calling the Router is implemented here.

Register the Router: register all the routers in the controller one by one, switch the TabBarIndex, and return to the Router. The callback is forwarded to the defined method.

Router processing: that is, after the Router is registered, when the corresponding Router is called, we write the callback method during registration. Here is the logic to perform the controller jump and pass parameters.

About controller hops: When we trigger the Router, we can get the Map that the Router maps to, get its Class, and initialize the instance by Class. We’re doing Push or Modal by finding the current visibleVC for the UIViewController Category, and we can also do Push or Modal based on the arguments that the business side is passing in and whether we need to animate or not;

About the transfer of ginseng: Passed in parameter is the dictionary data structures, so we test first instance VC does it include the attributes, [VC respondsToSelector: NSSelectorFromString (key)]. If VC has this attribute, use KVC to assign the value directly. In order to prevent some bugs caused by the mismatch between the dictionary Key and VC attribute in the development process, add a layer of NSAssert, so that the problem can be found faster in the development process!

The jump logic of the controller encapsulated by the author may be ill-considered, and the specific judgment should be made according to the specific business needs.

The following are the callback processing codes for registering the Router and matching the Router. Please read the Router registration code patiently and unify the three types of callback processing

Implementation JSDVCRouter (Handle) The jump of the controller + UITabBarIndex switch back + + page (void) load {[self performSelectorOnMainThread: @ the selector (registerRouter) withObject:nilwaitUntilDone:false]; } + (void)registerRouter {// Get the global RouterMapInfo NSDictionary* RouterMapInfo = [JSDVCRouterConfig configMapInfo]; // Router corresponds to the controller path, which is used to register the Route. When the current Route is called, the callback will be performed. Callback parameter: the parameter passed in when Route is executed;for (NSString* router in routerMapInfo.allKeys) {
        
        NSDictionary* routerMap = routerMapInfo[router];
        NSString* className = routerMap[kJSDVCRouteClassName];
        if(JSDIsString(className)) {/* Register all controller routers using [JSDVCRouter openURL:JSDVCRouteAppear]; Push to AppearVC; [JSDVCRouter openURL:JSDVCRouteAppear parameters:@{kJSDVCRouteSegue: kJSDVCRouteSegueModal, @"name": @"jersey"}]; Modal to Appear VC and carry the parameter name; */ [self addRoute:router handler:^BOOL(NSDictionary * _Nonnull parameters) { RouterMap: the routeMap of the current Route Map; the Map we configured in RouterConfig; ** parameters: the parameters passed in when calling route; */return[self executeRouterClassName:className routerMap:routerMap parameters:parameters]; }]; } // Register the Router to the specified TabBar Index; Use [JSDVCRouter openURL: JSDVCRouteCafeTab] to switch to the Cafe Index [self addRoute: @"/rootTab/:index" handler:^BOOL(NSDictionary * _Nonnull parameters) {
        NSInteger index = [parameters[@"index"] integerValue]; // Handle UITabBarControllerIndex toggle; UITabBarController* tabBarVC = (UITabBarController* )[UIViewController jsd_rootViewController];if ([tabBarVC isKindOfClass:[UITabBarController class]] && index >= 0 && tabBarVC.viewControllers.count >= index) {
            UIViewController* indexVC = tabBarVC.viewControllers[index];
            if([indexVC isKindOfClass:[UINavigationController class]]) { indexVC = ((UINavigationController *)indexVC).topViewController; } / / the refs [self setupParameters: the parametersforViewController:indexVC];
            tabBarVC.selectedIndex = index;
            return YES;
        } else {
            returnNO; }}]; Router, Router, Use [JSDVCRouter openURL: kJSDVCRouteSegueBack] to return to previous page or [JSDVCRouter openURL: kJSDVCRouteSegueBack parameters:@{kJSDVCRouteBackIndex: @ page return to the first two (2)}] [self addRoute: kJSDVCRouteSegueBack handler: ^ BOOL (NSDictionary * _Nonnull parameters) {return [self executeBackRouterParameters:parameters];
    }];
    }
Copy the code

Callback after Router match: instantiate controller, assign parameter value, redirect page

#pragma mark - execute Router VC    // When the specified Router is found, the route callback logic is triggered. If NO registered Router is found, return NO. If necessary, you can register a callback that is not globally matched to the Router for exception handling. + (BOOL)executeRouterClassName:(NSString *)className routerMap:(NSDictionary* )routerMap parameters:(NSDictionary* // Intercepts Router mapping parameters, whether login is required to jump; BOOL needLogin = [routerMap[kJSDVCRouteClassNeedLogin] boolValue];if(needLogin && ! userIsLogin) { [JSDVCRouter openURL:JSDVCRouteLogin];returnNO; } // Initialize the controller uniformly, pass parameters and jump; UIViewController* vc = [self viewControllerWithClassName:className routerMap:routerMap parameters: parameters];if (vc) {
        [self gotoViewController:vc parameters:parameters];
        return YES;
    } else {
        returnNO; }} // Instantiate the controller based on the class name to which the Router is mapped; + (UIViewController *)viewControllerWithClassName:(NSString *)className routerMap:(NSDictionary *)routerMap parameters:(NSDictionary* )parameters { id vc = [[NSClassFromString(className) alloc] init];if(! [vc isKindOfClass:[UIViewController class]]) { vc = nil; }#if DEBUG// VC is not UIViewController, NSAssert(vc, @)"%s: %@ is not kind of UIViewController class, routerMap: %@",__func__ ,className, routerMap);
#endifThe parameter / / [self setupParameters: the parametersforViewController:vc];
    
    returnvc; } // assign the VC parameter + (void)setupParameters:(NSDictionary *)paramsforViewController:(UIViewController* )vc {
    
    for (NSString *key inparams.allKeys) { BOOL hasKey = [vc respondsToSelector:NSSelectorFromString(key)]; BOOL notNil = params[key] ! = nil;if (hasKey && notNil) {
            [vc setValue:params[key] forKey:key];
        }
        
#if DEBUG// Vc has no corresponding attribute, but passes a valueif ([key hasPrefix:@"JLRoute"]==NO &&
            [key hasPrefix:@"JSDVCRoute"]==NO && [params[@"JLRoutePattern"] rangeOfString:[NSString stringWithFormat:@": % @",key]].location==NSNotFound) {
            NSAssert(hasKey == YES, @"%s: %@ is not property for the key %@",__func__ ,vc,key);
        }
#endif}; } // Jump and parameter Settings; + (void)gotoViewController:(UIViewController *)vc parameters:(NSDictionary *)parameters { UIViewController* currentVC = [UIViewController jsd_findVisibleViewController]; NSString *segue = parameters[kJSDVCRouteSegue] ? parameters[kJSDVCRouteSegue] : kJSDVCRouteSeguePush; // Decide present or Push; Default value Push BOOL animated = parameters[kJSDVCRouteAnimated]? [parameters[kJSDVCRouteAnimated] boolValue] : YES; // Transition animation; NSLog(@"%s jump: %@ %@ %@",__func__ ,currentVC, segue,vc);
    
    if ([segue isEqualToString:kJSDVCRouteSeguePush]) { //PUSH
        if (currentVC.navigationController) {
            NSString *backIndexString = [NSString stringWithFormat:@"% @",parameters[kJSDVCRouteBackIndex]];
            UINavigationController* nav = currentVC.navigationController;
            if ([backIndexString isEqualToString:kJSDVCRouteIndexRoot]) {
                NSMutableArray *vcs = [NSMutableArray arrayWithObject:nav.viewControllers.firstObject];
                [vcs addObject:vc];
                [nav setViewControllers:vcs animated:animated];
                
            } else if ([backIndexString integerValue] && [backIndexString integerThe Value] < nav. ViewControllers. Count) {/ / remove a specified number of VC, in Push; NSMutableArray *vcs = [nav.viewControllers mutableCopy]; [vcs removeObjectsInRange:NSMakeRange(vcs.count - [backIndexStringintegerValue], [backIndexString integerValue])];
                nav.viewControllers = vcs;
                [nav pushViewController:vc animated:YES];
            } else{ [nav pushViewController:vc animated:animated]; }}else{/ /, since the navigation bar directly executed Modal BOOL needNavigation = parameters [kJSDVCRouteSegueNeedNavigation]? NO : YES;if (needNavigation) {
                UINavigationController* navigationVC = [[UINavigationController alloc] initWithRootViewController:vc];
                //vc.modalPresentationStyle = UIModalPresentationFullScreen;
                [currentVC presentViewController:navigationVC animated:YES completion:nil];
            }
            else{ //vc.modalPresentationStyle = UIModalPresentationFullScreen; [currentVC presentViewController:vc animated:animated completion:nil]; }}}else { //Modal
        BOOL needNavigation = parameters[kJSDVCRouteSegueNeedNavigation] ? parameters[kJSDVCRouteSegueNeedNavigation] : NO;
        if (needNavigation) {
            UINavigationController* navigationVC = [[UINavigationController alloc] initWithRootViewController:vc];
            //vc.modalPresentationStyle = UIModalPresentationFullScreen;
            [currentVC presentViewController:navigationVC animated:animated completion:nil];
        }
        else{ //vc.modalPresentationStyle = UIModalPresentationFullScreen; [currentVC presentViewController:vc animated:animated completion:nil]; }}}Copy the code

The Router should be jumped to by the controller.

To be added

In addition to frequent switching between controllers, there are also jumps to H5 or WebView, etc.; Outward jump of App includes Scheme startup, 3D Touch, UniversalLink, click notification, etc. We can uniformly use Router for effective management of these items including jump and page switch, making App more dynamic and coupling degree between modules lower.

  • Supports H5 forward
  • External Scheme starts the App
  • UniversalLink
  • 3D Touch Shortcut
  • Dynamically delivering RouterMap configuration in the background

The last

I hope this article is helpful to you. If there is something wrong, I hope you can leave a message to correct it. Emmmmm, every time you see such a long string of code, you can either skip it directly, or you can start Xcode Coding and Commond + R immediately after you read it carefully. In order to facilitate your understanding, if you think it is helpful to you, Please give us a Star thank you 😆!!!!! Study on the way, with you!!

The author of this article:JerseyWelcome to reprint, please indicate the source andThis article links

Reference-link

IOS componentization — Analysis of route Design

IOS architecture practices are dry

IOS application architecture: Componentization solutions

The componentization of Mogujie App

Combat Demo if you feel helpful, trouble everyone to a Star thank you 😆!!!!!