1. The background

As we know, the front-end domain locates the page by route. To jump to the corresponding page, we only need to access the corresponding route, which is very convenient. However, there has been no concept of routing in iOS. To jump to a page, you need to create an instance of the target page and then jump to the page through the navigation controller, which is very tedious. There is a requirement that when you click on a push message (or click on an area) you need to jump to any possible page. At first glance I was resisting, but the need seemed reasonable. As a group of aspiring developers we have officially launched the Hundred bottle jumper routing project.

Unified hop: the open() method of unified hop routing SDK can be used to jump to any page (the target page implementation method supports but is not limited to Native, Flutter, HTML5, wechat applets, system applications and other third-party applications).

2. What problems to solve

Traditionally, we start a project by sorting out the pain points, sorting out the problems to be solved, and then identifying the needs.

2.1 Page Hopping

As long as the route of the destination page is provided to the unified hop route SDK, the unified hop route SDK can accurately jump to the destination page, rather than jump through the navigation controller after creating the page instance in the traditional way.

2.2 Entry entry

When a page jumps, you need to carry some parameters to the target page instance so that the target page can correctly process the logic.

2.3 Sending Returned Values

Sometimes we need to return some of the values we are interested in when the target page is closed. For example: select the delivery address page, after selecting the address, you need to send the address information just selected back to the previous page.

2.4 Roll back to the specified route

Sometimes we need to go back to the specified page. Take releasing short videos as an example: First, enter the video shooting page (A) from the home page (M), enter the video editing page (B) after shooting, enter the release page (C) after editing, and return to the page (M) before shooting page (A) instead of the video editing page (B) after Posting.

2.5 Unified Routing rules

Basic idea:

  • One route corresponds to one page
  • The route should be a meaningful string
  • Routing rules should apply to all ends
  • Each end should bind the route to the page

Solution:

From the above thinking, it is easy to think that our routing specification can follow the front-end routing specification, which conforms to Restful URIs.

URI = Scheme :[//authority]path[? Query][#fragment]

See also: RFC3986

We define corresponding schemes for routes in different domains, including Native: Native, Flutter: Flutter, HTTP/HTTPS: HTTP/HTTPS, small program: WXMP, third-party application: TP, etc.

2.6 Cross-module API calls

Sometimes we need to access methods provided by other modules in the same way we would make a GET request to the WebServer. In the traditional mode, cross-module intermodulation requires reference to the target module, which is tedious and sometimes circular reference occurs.

2.7 Behavior Routing

Sometimes we need to configure click behavior (not page-jump behavior) for an area of the page. For example, there is an HTML5 page that requires Native sharing behavior to be activated when clicking on an area of the page.

2.8 Route interceptor

Sometimes routes need to be authenticated, parameter reconfigured, broken points, and redirected. So adding interceptor functionality to the unified hop routing SDK is a good solution.

2.9 Packet route Interceptor

Sometimes, we need to perform authentication, parameter restructuring, interruption points, and anti-addiction for a group of routes. So we added a grouping interceptor feature to the unified hop routing SDK.

2.10 Route Redirection

With iteration and technology updates, many pages are rewritten by other, more appropriate technologies, while the legacy code is still in the old path, and the cost of versioning and changing it to the new path is a consideration, as well as the risks that may be introduced. Redirection makes it possible to access new routes at no cost.

2.11 Different technology stacks do not interfere with each other (keep elegant)

When a route is passed into the unified hop routing SDK, the unified hop routing SDK should be able to distinguish which route scheduler the current route is dispatched to for scheduling. For example, the pages implemented by Native, Flutter, and HTML5 should be handed over to their respective route schedulers.

3. What apis are provided

Here is the design of the BBRouter key API:

NS_ASSUME_NONNULL_BEGIN @interface BBRouter : NSObject <BBNativeRouter, BBBlockRouter> @property (nonatomic, strong, class, readonly) BBRouterConfig *routerConfig; /// @param undefinedRouteHandle unified callback when the route is not defined + (void)setUndefinedRouteHandle:(void)setUndefinedRouteHandle:(void)setUndefinedRouteHandle:(void)setUndefinedRouteHandle:(void)setUndefinedRouteHandle:(void)setUndefinedRouteHandle:(void)setUndefinedRouteHandle:(void (^)(BBRouterParameter *))undefinedRouteHandle; /// set the callback to open the specified page. + (void)setWillOpenBlock:(void (^)(BBRouterParameter *))willOpenBlock; + (void)setDidOpenBlock:(void (^)(BBRouterParameter *))didOpenBlock; // @param Dispatcher // @param scheme scheme + (BOOL)registerRouterDispatcher:(id<BBRouterDispatcher>)dispatcher scheme:(NSString *)scheme; // @param path route /// @param action implementation + (BOOL)registerTask:(NSString) *)path action:(BBBlockDispatcherAction)action; @param path route + (BOOL)removeTask:(NSString *)path; #pragma mark - Route to a specified page Please use the following 👇 convenient method) / / / @ param parameter parameter + (void) routeWithRouterParameter (BBRouterParameter *) parameter; // @param URL page URI + (BOOL)canOpen:(NSString *) URL; // @param URL page URI + (BOOL)canOpen:(NSString *) URL; /// @param url page URI + (void)open:(NSString *)url; /// @param url page URI /// @param urlParams parameter json serializable data type (complex data structure can be transmitted when Scheme is native) + (void)open:(NSString *)url urlParams:(NSDictionary * __nullable)urlParams; /// @param URL Url /// @param urlParams carries parameters json serializable data type (complex data structure can be transmitted when Scheme is native) /// @param resultCallback + (void)open:(NSString *)url urlParams:(NSDictionary * __nullable)urlParams onPageFinished:(void (^ __nullable)(NSDictionary *result))resultCallback; /// @param URL Url /// @param urlParams carries parameters json serializable data type (complex data structure can be transmitted when Scheme is native) /// + (void)open:(NSString *)url urlParams:(NSDictionary *)  __nullable)urlParams exts:(NSDictionary * __nullable)exts onPageFinished:(void (^ __nullable)(NSDictionary *result))resultCallback; /// @param URL Url /// @param urlParams carries parameters json serializable data type (complex data structure can be transmitted when Scheme is native) /// Animated: Animated /// @param routerStyle Animated /// @param resultCallback + via this callback block when callback results are required (void)open:(NSString *)url urlParams:(NSDictionary * __nullable)urlParams exts:(NSDictionary * __nullable)exts routerStyle:(KBBRouterStyle)routerStyle onPageFinished:(void (^ __nullable)(NSDictionary *result))resultCallback; + (BOOL)registerClass:(Class) CLS withPath:(NSString *)path; + (BOOL)registerWithClassName:(NSString *)className andPath:(NSString *)path; /// Register the view controller with the class name, path as identifier, Check whether the parameter matches the route. + (BOOL)registerWithClassName:(NSString *)className andPath:(NSString *)path verifyBlock:(BOOL(^) __nullable)(NSString *path ,BBRouterParameter *routerParameter))verifyBlock; /// delete a registered view controller + (BOOL)removeRegisteredPath:(NSString *)path; #pragma mark - BlockDispatcher implementation // execute registered block, Synchronization returns /// @param url url // @param urlParams input parameter + (id _Nullable)invokeTaskWithUrl:(NSString *)url urlParams:(NSDictionary *)urlParams; // Execute the registered block, // @param url url // @param urlParams parameter /// @param error error message + (id _Nullable)invokeTaskWithUrl:(NSString *)url urlParams:(NSDictionary *)urlParams error:(NSError **)error; // @param group group name + (BOOL)addPath:(NSString *)path toGroup:(NSString *)group; // add a group of paths to the specified group /// @param paths group // @param group group name + (void)addPaths:(NSArray<NSString *> *)paths toGroup:(NSString *)group; // @param group group name + (NSArray<NSString *> *)pathsInGroup:(NSString *)group; // @param group group name // @param verifyBlock callback closure + (void)configGroup:(NSString *)group verifyBlock:(BOOL(^ __nullable)(NSString *path ,BBRouterParameter *routerParameter))verifyBlock; // back up + (UIViewController * _Nullable)backwardCompletion:(void (^ __nullable)(void))completion; /// @param Animated + (UIViewController * _Nullable)backwardAnimated:(BOOL) Animated Completion: (void (^ __nullable)(void))completion; // @param count + (void)backwardCount:(NSInteger)count completion: (void (^ __nullable)(void))completion; /// @param vc new view controller + (void)openVC:(UIViewController *)vc routerParameter:(BBRouterParameter *)parameter; / / / the current existence of routing the specified view controller instance / / / @ param path routing path + (UIViewController * _Nullable) containsRouteObjectByPath (nsstrings *) path; // @param Completion + (UIViewController *) // @Param Completion + (UIViewController * _Nullable)backwardVC:(UIViewController *)vc animatedBlock:(BOOL(^)(NSString *toppath))animatedBlock completion: (void (^__nullable)(void))completion; @end NS_ASSUME_NONNULL_ENDCopy the code

3.1 Registering/Removing a Route scheduler

The route scheduler is used to isolate routes from different domains for easy decoupling. Developers can easily define their own routing scheduler and implement their own routing logic when using the unified hop routing SDK.

There must be differences between the jump logic of Native page, Flutter page and HTML5 page. In this case, the corresponding route scheduler should realize the jump behavior. Of course, “behavior routing” also has a corresponding route scheduler.

You can imagine what the routing SDK can do, just define a routing scheduler that works for you.

3.2 Registering or Removing the binding relationship between the route and the page

We need to register the binding relationship between the route and the page with the unified hop routing SDK to allow the unified hop routing SDK to dynamically resolve the route, dynamically generate the page instance and realize the automatic jump.

Note: This registry should allow updates to dynamically update the routing table.

It doesn’t have to be a page to register, it can be a Service. For example, the target page is provided by a third party and can only be opened by calling a method of the corresponding SDK. It is impossible to register the page directly. At this point, we can register a service for transfer. When the service is called, we can call the corresponding method of SDK to easily realize the above requirements.

3.3 Opening a Route and Obtaining the Returned Value

The unified hop routing SDK should provide a method to open a page (or call a method) and provide a callback to get the return value.

Input parameters should have the following:

  • Uri: routing
  • Parameters: the input parameter to be carried
  • Exts: other parameters (non-business parameters, such as specifying the transition animation mode)
  • Callback: callback function pointer

3.4 Roll Back to the Previous page

The unified hop routing SDK should provide a way to return to the previous page.

3.5 Roll back to the specified page

The unified hop routing SDK should provide a way to fall back to the specified route in the fallback stack and return an instance of the specified route. The unified hop routing SDK should provide a method to roll back Layer N routes.

3.6 Troubleshooting routes That are Not Found

When a message pushes a page specific to the new version, the old version should go to a unified exit where the route is not found, where you can do a redirection or prompt the user to upgrade to the latest version.

4. Key ideas and norms

We’ve sorted out the requirements, and it’s time to design the architecture of the unified hop routing SDK.

4.1 How to Achieve High Scalability

Rational abstraction and functional separation are the foundation of high scalability.

We designed the abstract concept of route scheduler, which is used to isolate routes from different domains.

Native routes, Flutter routes, HTML5 routes, and small program routes are scheduled by corresponding route schedulers respectively. Behavioral routes are also scheduled by the corresponding route scheduler.

4.2 How to avoid intrusion and coupling

When the unified hop routing SDK was established, our project had already reached a certain scale. If the access to unified hop routing SDK needs to modify the existing business code, it will undoubtedly be a disaster. So we had to be perfectly compatible with traditional development methods and avoid introducing extra work and learning costs to our members.

As you can imagine, we are almost perfectly compatible with traditional development methods, see the Unified hop Routing SDK (iOS implementation).

4.3 Scientifically manage routing tables

  • Centralized routing table management
  • Version Management (for dynamic routing table delivery)
  • Routing tables should be marked with route names, purpose descriptions, incoming and outgoing parameters, and other additional restrictions (such as permissions needed to access the page).

4.3.1 Optimization of user experience

Generate each side code through the script:

Avoid hard coding: Avoid hard coding by mapping the routing table to a structure where each route is an attribute.

Input parameter constructor: The input parameter is a dictionary, and we can generate the constructor for the dictionary based on the input parameter in the route definition.

Outgoing parameter: The outgoing parameter is a dictionary, and we can automatically generate the associated attributes of the dictionary based on the routing table.

Version management: The routing table repository automatically executes the script after tagging to generate each side of the code (not expanded in this article).

4.4 Dynamic Routing Table Delivery

The configuration center provides the capability to update the routing table. Each end updates the routing table according to the specified policy.

5. Unified hop Routing SDK (iOS terminal implementation)

5.1 Compatible with native development mode

Taking the traditional iOS development mode as an example, the following steps are required to jump to a new page:

  1. Create the target ViewController instance
  2. Input parameters are passed as ViewController instance property assignments
  3. Get the appropriate NavigationController instance (or ViewController instance if transition mode is modal)
  4. NavigationController instances push to new pages (or ViewController modal to new pages)
  5. Returns a value in block or delegate mode

The above method is sufficient for most scenarios, so let’s think about how to implement the above steps in an elegant way:

  1. The URI is bound to the ViewController class as a key-value pair, and the ViewController instance is dynamically generated by objective-C Runtime.
  2. The URI is carried as a Query (the input parameter is internally resolved to Dictionary by the UNIFIED hop routing SDK), the key is the ViewController property (or instance variable) name, The Objective-C Runtime is used to determine whether the ViewController class contains the attribute or instance variable, and whether the data type is consistent. If yes, the objective-C KVC method is used to assign a value to the attribute or instance variable, so as to achieve input parameter passing.
  3. The appropriate NavigationController instance (presented as the top ViewController instance) can be obtained by traversing the route rollback stack on the main Window.
  4. All of the above conditions are in place, and it is easy to realize the page jump.
  5. In terms of data return, we can return data via ViewController when removed (definitely notdeallocWhen, becausedeallocNot called in the event of a memory leak, which occasionally happens).

The above idea is clear and executable, but if you want to be more flexible and easy to use, you also need to cleverly associate ViewController instances with routing parameters.

We encapsulate routing-related parameters as routerParameters, structured as follows:

@interface RouterParameter: NSObject @property (nonatomic, copy) NSString * Scheme; @property (nonatomic, copy) NSString *fullPath; /// URI query @property (nonatomic, copy) NSString *query; /// URI fragment @property (nonatomic, copy) NSString *fragment; /// page jump (push/present) @property (nonatomic, assign) KBBRouterStyle routerStyle; @property (nonatomic, copy) NSString * URL; @property (nonatomic, strong, readonly) NSMutableDictionary *addition; @property (nonatomic, strong, readonly) NSMutableDictionary *exts; /// callback values (code, message, data) @property (nonatomic, strong) NSDictionary *response; @property (nonatomic, copy) void (^__nullable callBackBlock)(NSDictionary *result);Copy the code

Void open:(NSString *) urlParams:(NSDictionary * __nullable)urlParams exts:(NSDictionary * __nullable)exts routerStyle:(KBBRouterStyle)routerStyle onPageFinished:(void (^ __nullable)(NSDictionary *result))resultCallback The related parameters carried by the method are converted into RouterParameter instances and passed inside the unified hop routing SDK. Add property routerParameter to UIViewController via UIViewController Category (Category) and Objective-C “associate object”.

At this point we can see that the above “thinking” has been implemented, the thinking is clear, and perfectly compatible with the native development model. This enables a painless gradual switch from traditional mode to “routing mode”.

5.2 architecture

6. How to use it

A quick look at the Demo gives you a more intuitive view of a framework, so let’s take a look at general usage and usage.

6.1 Overall Process

Initialization phase:

  • Loading a Routing Table
  • Register a route interceptor
  • Native Route Registration
  • Non – page route registration
  • Group interceptor registration

Ready stage: At this point, the unified hop route SDK is ready.

6.2 Page-class routing calls

Own Native page:

Routing registered


// Register the Objective-C implementation ViewController
BBRouter.register(withClassName: "MomentsViewController", andPath: BBRouterPaths.moments)

// Register Swift implementation ViewController (note namespace)
BBRouter.register(withClassName: swiftClassFullName("MomentsViewController"."Community"), andPath: BBRouterPaths.moments)

Copy the code

Pages implemented by Flutter/HTML5 are not registered here and are managed by the Flutter/HTML5 project itself

Routing hop

/ / there is no return value routing jump [BBRouter open: BBRouterPaths. Moments urlParams: @ {@ "momentId:" @ "11223344"}); / / returns a value routing jump (BBRouterPaths selectAlcohol this page can be any kind of technical implementation, such as: Native [Swift \] Objective - C, Flutter, HTML 5) [BBRouter open: BBRouterPaths. SelectAlcohol UrlParams :@{@"alcoholId":@"112233"} onPageFinished:^(NSDictionary * _Nonnull Result) {// r_data is through Objective-C DEBUGLog(@"%@", [NSString stringWithFormat:@"%@", result.r_data]);}];Copy the code

BBRouterPaths. SelectAlcohol: use this way to kill routing hard coded. Direct hard coding cannot be checked by the compiler and is costly to maintain. One of the design goals of the unified hop routing SDK was to eliminate hard coding.

6.2 Method/behavior class routing calls

[BBRouter registerTask:@"action://xxx.com/yyy/zzz" action:^id _Nullable(BBRouterParameter * _Nonnull routerParameter) { return routerParameter.addition; }]; // call the method asynchronously. [BBRouter open:@" Action ://xxx.com/yyy/zzz" urlParams:@{@"name":@"xiaoming"} onPageFinished:^(NSDictionary * _Nonnull result) { DEBUGLog(@"%@", [NSString stringWithFormat:@"%@", result]); }]; // method synchronous call (event-specific method for routing) NSError *error = nil; id result = [BBRouter invokeTaskWithUrl:@"action://xxx.com/yyy/zzz" urlParams:@{@"name":@"xiaoming"} error:&error]; DEBUGLog(@"%@", [NSString stringWithFormat:@"%@", result]);Copy the code

Three party application specified page:

Unpack the.ipa files of Taobao and Tmall, analyze their routing table and call rules, and with a try attitude, find that our unified hop routing SDK also supports perfectly.

/ / taobao commodity details page [BBRouter open: BBRouterPaths. ThreeSides urlParams:@{@"i":@"taobao://item.taobao.com/item.htm?id=554418184878"}]; / / Tmall commodity details page [BBRouter open: BBRouterPaths. ThreeSides urlParams: @ {@ "I" : @ "tmall: / / page. Tm/itemDetail? ItemID = 551101867384"});Copy the code

6.3 Simple demonstration of route interceptor

Parameter refactoring: Id is a keyword in Objective-C, but can be used in other languages. You can do an input refactoring in the interceptor to accommodate this scenario.

BBRouter.register(withClassName: "XXXViewController", andPath: BBRouterPaths.xxx, verifyBlock: { path, routerParameter in
    routerParameter.addition["ID"] = routerParameter.addition["id"]
    return true
})

Copy the code

Redirection: The page is refactored using a new technique, and the new version should jump to the new page. With redirection, we don’t have to modify the existing code. The runtime is redirected to the new page even if the old code still hops the old route.

BBRouter.register(withClassName: "XXXViewController", andPath: BBRouterPaths.xxx, verifyBlock: { path, routerParameter in
    let newParameter = BBRouterParameter(byURI: BBRouterPaths.yyy, addition: routerParameter.addition.copy() as! [String : Any])
    newParameter.actionBlock = routerParameter.actionBlock
    newParameter.routerStyle = routerParameter.routerStyle
    newParameter.exts.addEntries(from: routerParameter.exts as! [String : Any])
    BBRouter.route(with: newParameter)

    return false
})

Copy the code

6.4 Simple demonstration of route block interceptor function

Block interceptors are used here to implement the requirement that a set of pages need to be logged in successfully before they can be accessed, and to achieve consistency in user actions.


let isAuthed = "isAuthed"
BBRouter.addPaths(needAuthedPaths, toGroup: isAuthed);
BBRouter.configGroup(isAuthed) { (path, routerParameter) -> Bool in
    if (memberId.isEmpty) {
        BBRouter.open(BBRouterPaths.login, urlParams: Dictionary(), exts: Dictionary()) { (result) in
            if (!memberId.isEmpty) {// Continue if you are logged in
                BBRouter.route(with: routerParameter)
            }
        }
        return false;
    }
    return true
}

Copy the code

6.5 Handling the Unregistered Route

// This is where unregistered routing information can be handed to the HTML5 landing page, which is flexible enough to redirect and prompt the user to upgrade.


BBRouter.setUndefinedRouteHandle { (parameter) in
    let url = parameter.url
    BBRouter.open(BBRouterPaths.routerNotFound, urlParams: ["url":url])
}

Copy the code

summary

The SDK of 100-bottle unified hop routing makes unified hop a reality and lays a foundation for the visual construction of the page. So far, it has been delivered and used for about a year. It has played an important role in promoting the componentization/modularization process, and has well fulfilled the goal of “decoupling and improving efficiency” when establishing the project. Even better is the painless and gradual transition from traditional mode to “routing mode” on iOS, which costs almost nothing to access.

Due to space constraints, many important implementation details are not covered, nor are many application scenarios. On the other hand, I don’t want to talk about the details too thoroughly, so as not to affect our thinking.

Finally, I sincerely hope that you can point out the shortcomings of the scheme and put forward new optimization suggestions. If you have any questions, please leave a comment below and we will reply as soon as possible. If this article inspires you a little bit, please click “like”, and if you can share it with your friends, thank you even more.

More exciting, please pay attention to our public number “100 bottle technology”, there are not regular benefits!