1. Background
We know that the front-end domain uses routes to locate pages. To jump to the corresponding page, you only need to access the corresponding route, which is very convenient. However, there has been no routing concept in the iOS field for a long time. To jump to a page, you need to create an instance of the target page, and then jump through the navigation controller, which is very cumbersome. There is such a requirement: when you click on the push message (or click on a certain area), you need to jump to any possible page. At first glance I was resistant, but the demand seemed reasonable. As a group of aspiring developers, we officially launched the project.
Unified jump: that is, through the open()
method of the unified jump routing SDK, the purpose of jumping to any page is achieved (the target page implementation method supports but is not limited to Native, Flutter, HTML5, WeChat applet, system application and other third-party applications).
2. What problems to solve
As usual, when starting a project, we first sort out the pain points, sort out the problems to be solved, and then determine the requirements.
2.1 Page Jump
The page jump only needs to provide the destination page route to the unified jump routing SDK, and the unified jump routing SDK can jump to the destination page accurately, instead of creating a page instance and jumping through the navigation controller in the traditional way.
2.2 Entry into the ginseng
When the page jumps, you need to carry some parameters to the target page instance, so that the target page can process the logic correctly.
2.3 Return value return
Sometimes we need to pass back some value we are interested in when the target page is closed. For example: select the delivery address page, after selecting the address, you need to return the address information just selected to the previous page.
2.4 Fallback to the specified route
Sometimes we need to fall back to a specified page. Take publishing a short video 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 publishing page (C) after editing, and return to the shooting page after publishing the video. (A) The previous page (M) instead of the video editing page (B).
2.5 Unified routing rules
Basic idea :
- A route corresponds to a page
- route should be a meaningful string
- Routing rules should apply to each end
- Each end should bind the route to the page
scheme :
From the above ideas, it is easy to think that our routing specification can follow the front-end routing specification, which is consistent with the Restful
URI
Both: URI = scheme:[//authority]path[?query][#fragment]
See also:
We define corresponding schemes for routes in different fields, namely Native: native, Flutter: flutter, http/https: http/https, applet: wxmp, third-party application: tp (third party), etc.
2.6 Cross-module API calls
Sometimes we need to access methods provided by other modules like a GET request to WebServer. In the traditional mode, intermodulation of cross-module methods needs to refer to the target module, which is very cumbersome and sometimes circular references occur.
2.7 Behavioral routing
Sometimes we need to configure click behavior (not page jump behavior) for a certain area of the page. For example: There is an HTML5 page that needs to invoke the native sharing behavior when clicking on a certain area on the page.
2.8 Route Interceptor
Sometimes we need to authenticate routes, reconfigure parameters, break points, redirects, etc. Therefore, adding an interceptor function to the unified hop routing SDK is a good solution.
2.9 Packet Routing Interceptor
Sometimes we need to authenticate a group of routes, reconfigure parameters, interrupt points, and prevent addiction. Therefore, we add the packet interceptor function to the unified hop routing SDK.
2.10 Route redirection
With continuous iteration and technology updates, many pages will be rewritten by other more suitable technologies. At this time, the historical code is still using the old route. If you want to change the old code to a new route, you must consider version control and change costs. and the risks that may be introduced. With the redirection function, you can switch to a new route at no cost.
2.11 Different technology stacks do not interfere with each other (keep elegance)
When a route is passed into the unified hop routing SDK, the unified hop routing SDK should be able to distinguish which routing scheduler to distribute the current route to for scheduling. For example, the pages implemented by Native, Flutter, and HTML5 should be handed over to their respective routing schedulers.
3. What APIs are provided
Below is the design of BBRouter's key API :
NS_ASSUME_NONNULL_BEGIN
@interface BBRouter : NSObject <BBNativeRouter, BBBlockRouter>
@property (nonatomic, strong, class, readonly) BBRouterConfig *routerConfig;
/// 设置跳转未定义路由时的统一回调
/// @param undefinedRouteHandle 未定义路由时的统一回调
+ (void)setUndefinedRouteHandle:(void (^)(BBRouterParameter *))undefinedRouteHandle;
/// 设置将要打开指定页面的回调
+ (void)setWillOpenBlock:(void (^)(BBRouterParameter *))willOpenBlock;
/// 设置已经打开指定页面的回调
+ (void)setDidOpenBlock:(void (^)(BBRouterParameter *))didOpenBlock;
#pragma mark - 注册路由调度器 Dispatcher
/// 注册路由调度器
/// @param dispatcher 调度器
/// @param scheme scheme
+ (BOOL)registerRouterDispatcher:(id<BBRouterDispatcher>)dispatcher scheme:(NSString *)scheme;
#pragma mark - BBBlockRouter
/// 注册路由的实现 block
/// @param path 路由
/// @param action 实现
+ (BOOL)registerTask:(NSString *)path action:(BBBlockDispatcherAction)action;
/// 移除已注册的 block
/// @param path 对应的路由
+ (BOOL)removeTask:(NSString *)path;
#pragma mark - 路由跳转 API
/// 路由到指定页面(该方法为底层方法,不建议直接使用,请使用下面的👇便捷方法)
/// @param parameter 参数
+ (void)routeWithRouterParameter:(BBRouterParameter *)parameter;
#pragma mark - 已经存在 URL 的情况 页面跳转
/// 判断能否打开指定 URI
/// @param url 页面 URI
+ (BOOL)canOpen:(NSString *)url;
/// 打开页面
/// @param url 页面 URI
+ (void)open:(NSString *)url;
/// 打开页面并携带参数
/// @param url 页面 URI
/// @param urlParams 携带的参数 json 可序列化的数据类型(当 scheme 为 native 时 可以传递复杂数据结构)
+ (void)open:(NSString *)url urlParams:(NSDictionary * __nullable)urlParams;
/// 打开页面并携带参数且支持数据回传
/// @param url url
/// @param urlParams 携带的参数 json 可序列化的数据类型(当 scheme 为 native 时 可以传递复杂数据结构)
/// @param resultCallback 当需要回调结果时 通过该回调block实现
+ (void)open:(NSString *)url urlParams:(NSDictionary * __nullable)urlParams onPageFinished:(void (^ __nullable)(NSDictionary *result))resultCallback;
/// 打开页面并携带参数且支持数据回传
/// @param url url
/// @param urlParams 携带的参数 json 可序列化的数据类型(当 scheme 为 native 时 可以传递复杂数据结构)
/// @param exts 额外参数
/// @param resultCallback 当需要回调结果时通过该回调 block 实现
+ (void)open:(NSString *)url urlParams:(NSDictionary * __nullable)urlParams exts:(NSDictionary * __nullable)exts onPageFinished:(void (^ __nullable)(NSDictionary *result))resultCallback;
/// 打开页面并携带参数且支持数据回传
/// @param url url
/// @param urlParams 携带的参数 json 可序列化的数据类型(当 scheme 为 native 时 可以传递复杂数据结构)
/// @param exts 额外参数 animated:是否有过渡动画
/// @param routerStyle 过渡动画
/// @param resultCallback 当需要回调结果时通过该回调 block 实现
+ (void)open:(NSString *)url urlParams:(NSDictionary * __nullable)urlParams exts:(NSDictionary * __nullable)exts routerStyle:(KBBRouterStyle)routerStyle onPageFinished:(void (^ __nullable)(NSDictionary *result))resultCallback;
#pragma - mark BBNativeRouter
///通过类注册视图控制器,path 为标识
+ (BOOL)registerClass:(Class)cls withPath:(NSString *)path;
///通过类名注册视图控制器 SDK,path 为标识
+ (BOOL)registerWithClassName:(NSString *)className andPath:(NSString *)path;
///通过类名注册视图控制器,path 为标识,确认参数是否匹配该路由
+ (BOOL)registerWithClassName:(NSString *)className andPath:(NSString *)path verifyBlock:(BOOL(^ __nullable)(NSString *path ,BBRouterParameter *routerParameter))verifyBlock;
///删除注册的视图控制器
+ (BOOL)removeRegisteredPath:(NSString *)path;
#pragma mark - BlockDispatcher 相关实现
/// 执行已注册的 block,同步返回
/// @param url url
/// @param urlParams 入参
+ (id _Nullable)invokeTaskWithUrl:(NSString *)url urlParams:(NSDictionary *)urlParams;
/// 执行已注册的 block,同步返回
/// @param url url
/// @param urlParams 入参
/// @param error 出错信息
+ (id _Nullable)invokeTaskWithUrl:(NSString *)url urlParams:(NSDictionary *)urlParams error:(NSError **)error;
#pragma mark - 分组
/// 添加 path 到指定分组
/// @param path 路由路径
/// @param group 分组名称
+ (BOOL)addPath:(NSString *)path toGroup:(NSString *)group;
/// 添加一组路径到指定分组
/// @param paths 路径分组
/// @param group 分组名称
+ (void)addPaths:(NSArray<NSString *> *)paths toGroup:(NSString *)group;
/// 指定分组下所有路由
/// @param group 分组名
+ (NSArray<NSString *> *)pathsInGroup:(NSString *)group;
/// 配置分组的回调函数 用以处理分组逻辑
/// @param group 分组名称
/// @param verifyBlock 回调闭包
+ (void)configGroup:(NSString *)group verifyBlock:(BOOL(^ __nullable)(NSString *path ,BBRouterParameter *routerParameter))verifyBlock;
///回退
+ (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 新的视图控制器
+ (void)openVC:(UIViewController *)vc routerParameter:(BBRouterParameter *)parameter;
/// 当前是否存在路由指定的视图控制器实例
/// @param path 路由路径
+ (UIViewController * _Nullable)containsRouteObjectByPath:(NSString *)path;
/// 返回到指定页面的上一级
/// @param vc 指定的视图控制器实例
/// @param animatedBlock 是否需要动画
/// @param completion 完成
+ (UIViewController * _Nullable)backwardVC:(UIViewController *)vc animatedBlock:(BOOL(^)(NSString *toppath))animatedBlock completion: (void (^__nullable)(void))completion;
@end
NS_ASSUME_NONNULL_END
3.1 Register/Remove Route Scheduler
The route scheduler is used to isolate routes in different fields and facilitate decoupling. When developers use the unified hop routing SDK , they can easily define their own routing scheduler and implement their own routing logic.
There must be differences in the jump logic between Native pages, Flutter pages, and HTML5 pages. At this time, there should be a corresponding routing scheduler to implement the jump behavior. Of course, "behavior routing" also has a corresponding routing scheduler.
In short, you can use your imagination to fully utilize the capabilities of the unified hop routing SDK , you only need to define a routing scheduler that suits you.
3.2 Register/Remove the binding relationship between routes and pages
We need to register the binding relationship between the route and the page to the unified hop routing SDK to allow unified hop routing SDK to dynamically parse the route, dynamically generate page instances and realize automatic jumps.
should note : This registration should allow updates to enable dynamic routing table updates.
The registration does not have to be a page, it can also be a service (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 transit, and when the service is called, we can easily implement the above requirements by calling the corresponding method of the SDK.
3.3 Open a route and get the return value
The Unijump Routing SDK should provide a method to open a certain page (or call a certain method) and provide a callback to get the return value.
parameters should have the following :
- uri: route
- parameters: input parameters to be carried
- exts: other parameters (non-business parameters, such as: specifying the transition animation method)
- callback: callback function pointer
3.4 Go back to the previous page
The Unified Hop Routing SDK should provide a method to return to the previous page.
3.5 Back to the specified page
The unified-hop routing SDK should provide a method 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 fall back to N-tier routing.
3.6 Handling of route not found
When the message pushes a page specific to the new version, the old version should enter the unified exit where the route is not found. You can do a redirect here or prompt the user to upgrade to the latest version.
4. Key ideas and norms
We have straightened out the requirements, and then we will design the architecture of the unified hop routing SDK .
4.1 How to achieve high scalability
Reasonable abstraction and functional splitting are the basis for achieving high scalability.
We designed the abstract concept of route scheduler, which is used to isolate routes in different fields.
Native routing, Flutter routing, HTML5 routing, applet routing, etc., have corresponding routing schedulers to implement scheduling. behavior route is also scheduled by the corresponding route scheduler.
4.2 How to avoid intrusion and coupling
When the unified hop routing SDK approved, our project already had a certain scale. If we need to modify the existing business code to access unified hop routing SDK , it will undoubtedly be a disaster. Therefore, we must be perfectly compatible with traditional development methods and avoid introducing additional workload and member learning costs.
As you can imagine, our almost perfectly compatible with the traditional development method . For details, please refer Unified Jump Routing SDK (iOS implementation) .
4.3 Scientific management routing table
- Routing table centralized management
- Version management (applied to the dynamic routing table)
- The routing table should be marked with the routing name, purpose description, input parameters, output parameters, and other additional restrictions (such as the permissions required to enter the page)
4.3.1 User experience optimization
generates each end code through the script:
Avoid hard coding: The routing table is mapped as a structure, each route is an attribute, in this way hard coding is avoided.
Input parameter constructor: The input parameter is a dictionary, and we can generate the constructor corresponding to the dictionary according to the input parameters when the route is defined.
Outgoing parameter: Outgoing parameter is a dictionary, and we can automatically generate the associated attributes of the dictionary according to the routing table.
Version management: After the routing table warehouse is tagged, the script is automatically executed to generate the code of each end (this article will not expand).
4.4 Dynamic Delivery of Routing Table
The configuration center provides the ability to update the routing table, and each end updates the routing table according to the agreed policy.
5. Unified hop routing SDK (iOS implementation)
5.1 Compatible with native development methods
Taking the traditional iOS development method as an example, jumping to a new page requires the following steps :
- Create the target ViewController instance
- The input parameters are passed in the way of assignment of ViewController instance properties
- Get the appropriate NavigationController instance (if the transition is modal, you need to get the appropriate ViewController instance)
- The NavigationController instance jumps to a new page by push (or ViewController modally jumps to a new page)
- Return value as block or delegate
The above methods of have been able to meet most of the scenarios. Let's think about how to implement the above steps in an elegant way: :
- Bind the URI to the ViewController class in the form of key-value pairs, and dynamically generate the ViewController instance with the help of Objective-C runtime.
- The URI carries the input parameter in the form of Query (the input parameter will be parsed as a Dictionary in the system jump routing SDK), the key is the name of the ViewController property (or instance variable), and the Objective-C runtime is used to determine whether the ViewController class contains the property or instance variable. And judge whether the data type conforms, and if so, assign value to the property or instance variable through Objective-C KVC, so as to realize the parameter transfer.
- By traversing the routing back stack on the main Window (not necessarily the keyWindow, depending on the actual situation), you can obtain the appropriate NavigationController instance (the top ViewController instance when present).
- All the above conditions are met, and the page jump can be easily realized at this time.
- Regarding data return, we can pass back when the ViewController is removed (it must not be
dealloc
, becausedealloc
will not be called when memory leaks, and memory leaks occasionally occur).
The above ideas are clear and executable, but if you want to be more flexible and easy to use, you need to skillfully connect the ViewController instance with the routing-related parameters.
We encapsulate the routing-related parameters as class RouterParameter
, and the structure is as follows: :
@interface RouterParameter : NSObject
/// 路由所属领域(由哪个路由调度器调度)
@property (nonatomic, copy) NSString *scheme;
/// 路由路径(不包含 query 和 fragment 部分)
@property (nonatomic, copy) NSString *fullPath;
/// URI query 部分
@property (nonatomic, copy) NSString *query;
/// URI fragment 部分
@property (nonatomic, copy) NSString *fragment;
/// 页面跳转方式(push/present)
@property (nonatomic, assign) KBBRouterStyle routerStyle;
/// 完整 URI(会把 addition 拼接入 query)
@property (nonatomic, copy) NSString *url;
/// 路由入参
@property (nonatomic, strong, readonly) NSMutableDictionary *addition;
/// 额外参数(路由行为参数,如:是否开启转场动画)
@property (nonatomic, strong, readonly) NSMutableDictionary *exts;
/// 回调值(code、message、data)
@property (nonatomic, strong) NSDictionary *response;
/// 回传值使用的回调函数
@property (nonatomic, copy) void (^__nullable callBackBlock)(NSDictionary *result);
Convert the relevant parameters carried by the + (void)open:(NSString *)url urlParams:(NSDictionary * __nullable)urlParams exts:(NSDictionary * __nullable)exts routerStyle:(KBBRouterStyle)routerStyle onPageFinished:(void (^ __nullable)(NSDictionary *result))resultCallback
method into a RouterParameter instance and pass it inside the Route SDK, and add the attribute routerParameter
to UIViewController by means of UIViewController Category and Objective-C "Associated Object".
At this moment, we will find that the above "ideas" have been implemented, the ideas are clear and easy to understand, and are perfectly compatible with the native development model. Thereby, the traditional mode of can be gradually switched to the "routing mode" .
5.2 Architecture
6. How to use
A quick look at the Demo gives a more intuitive understanding of a framework, let's take a look at common usage and usage.
6.1 Overall Process
initialization phase :
- Load routing table
- Register a route interceptor
- Native route registration
- Off-page routing registration
- Packet interceptor registration
ready stage : At this point, the unified hop routing SDK is ready.
6.2 Page class routing call
its own native page :
route registration
// 注册 Objective-C 实现的 ViewController
BBRouter.register(withClassName: "MomentsViewController", andPath: BBRouterPaths.moments)
// 注册 Swift 实现的 ViewController(注意命名空间)
BBRouter.register(withClassName: swiftClassFullName("MomentsViewController", "Community"), andPath: BBRouterPaths.moments)
Pages implemented by Flutter/HTML5 are not registered here and are managed by the Flutter/HTML5 project itself
route jump
// 无返还值路由跳转
[BBRouter open:BBRouterPaths.moments urlParams:@{@"momentId":@"11223344"}];
// 有返回值路由跳转(BBRouterPaths.selectAlcohol 这个页面可能是任意一种技术实现的如:Native[Swift\Objective-C]、Flutter、HTML5 等)
[BBRouter open:BBRouterPaths.selectAlcohol urlParams:@{@"alcoholId":@"112233"} onPageFinished:^(NSDictionary * _Nonnull result) {
// r_data 是通过 Objective-C 的 Category 和关联对象方式为 NSDictionary 添加的属性,从而干掉硬编码。
DEBUGLog(@"%@", [NSString stringWithFormat:@"%@", result.r_data]);
}];
BBRouterPaths.selectAlcohol: Use this method to hard-code routes. Direct hard-coding cannot use compiler checks, and the maintenance cost is high. One of the design goals of the Unified Hop Routing SDK is to eliminate the hard-coded .
6.2 Method/Behavior Class Routing Calls
// 注册行为
[BBRouter registerTask:@"action://xxx.com/yyy/zzz" action:^id _Nullable(BBRouterParameter * _Nonnull routerParameter) {
return routerParameter.addition;
}];
// 方法异步调用(统跳统一方法进行路由,不区分路由所属领域)
[BBRouter open:@"action://xxx.com/yyy/zzz" urlParams:@{@"name":@"xiaoming"} onPageFinished:^(NSDictionary * _Nonnull result) {
DEBUGLog(@"%@", [NSString stringWithFormat:@"%@", result]);
}];
// 方法同步调用(事件专用方法进行路由)
NSError *error = nil;
id result = [BBRouter invokeTaskWithUrl:@"action://xxx.com/yyy/zzz" urlParams:@{@"name":@"xiaoming"} error:&error];
DEBUGLog(@"%@", [NSString stringWithFormat:@"%@", result]);
Three-party application specified page :
Unpack the .ipa
files of Taobao and Tmall, analyze their routing tables and calling rules, and find that our unified hop routing SDK also perfectly supports it.
// 淘宝商品详情页
[BBRouter open:BBRouterPaths.threeSides urlParams:@{@"i":@"taobao://item.taobao.com/item.htm?id=554418184878"}];
// 天猫商品详情页
[BBRouter open:BBRouterPaths.threeSides urlParams:@{@"i":@"tmall://page.tm/itemDetail?itemID=551101867384"}];
6.3 Simple demonstration of route interceptor
parameter rearrangement : id
is a keyword in Objective-C, but other languages can be used normally. In order to be compatible with this scenario, you can do a parameter rearrangement operation in the interceptor.
BBRouter.register(withClassName: "XXXViewController", andPath: BBRouterPaths.xxx, verifyBlock: { path, routerParameter in
routerParameter.addition["ID"] = routerParameter.addition["id"]
return true
})
redirects : The page is refactored using new technology, and the new version should jump to a new page. With the redirection capability, we don't need to modify the existing code. Even if the old code jumps to the old route, the runtime will be redirected to the new page.
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
})
6.4 Simple demonstration of routing packet interceptor function
Here, the group interceptor is used to achieve the requirement that a group of pages need to be successfully logged in before they can be accessed, and the continuity of user operations is achieved.
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) {// 如果已登录 则继续之前的操作
BBRouter.route(with: routerParameter)
}
}
return false;
}
return true
}
6.5 Route unregistered processing
// You can hand over unregistered routing information to the HTML5 landing page here, which is very flexible at this time, you can do redirection or prompt the user to upgrade.
BBRouter.setUndefinedRouteHandle { (parameter) in
let url = parameter.url
BBRouter.open(BBRouterPaths.routerNotFound, urlParams: ["url":url])
}
summary
Hundred Bottles Hop Routing SDK makes Hop a reality and lays the foundation for page visualization. It has been delivered and used for about a year so far, which has an important role in promoting the componentization/modularization process, and has successfully completed the goal of "decoupling and improving efficiency" when the project was established. What is even more gratifying is that the iOS terminal can from the traditional mode to the "routing mode" and gradually, and the access process is almost zero cost.
Due to the limited space, many important implementation details are not mentioned, and many application scenarios are not mentioned. On the other hand, I don’t want to make the details too thorough, so as not to be preconceived and affect everyone’s thinking.
Finally, I sincerely hope that you can point out the shortcomings of the program and put forward new optimization suggestions. If you have any questions, you can leave a message below the article, we will reply as soon as possible. If this article can inspire you a little, please like it. If you can share it with your friends, I will be even more grateful.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。