iOS dynamic routing implementation scheme

Background & Pain Points

With the expansion of the CSDN App technology stack, the jump from the simplest native page to the native page has been extended to the jump between native pages, H5 pages, applet pages, and Flutter pages in the same App. insert image description hereThe problem caused by this is: with the iteration of the App version, many pages that were originally implemented natively need to be upgraded/downgraded through the new H5 page, or the original PC or H5 page needs to be redirected to the existing native page. And these are basically hard-coded jump logic, which needs to be constantly changed with the version. To sum up, the existing direct problems faced by the page jump logic isolated by each technology stack are:

  • The logic of jumping needs to be iteratively based on the version, and cannot be changed remotely in a unified way (for example: every time a page that needs to be intercepted and jumped to the original page is added, it needs to be solved by issuing a version)
  • The implementation cost of jumping across technology stacks is relatively high, and special adaptation must be made in the bridge module
  • In H5 pages, some jumps need to jump to native or other pages, and special judgment processing must be made through the interception of WebView jumps

In order to solve the above problems of hard coding and poor flexibility, we integrate these jumps by combing the existing technology stack jump logic, which can meet the needs of dynamic and configurable.

The meaning of routing

The first thing to be clear is that routing does not just refer to interface jumping, but also data acquisition and business processing.

front-end routing

The concept of routing in the network refers to the process that a router receives a data packet from one interface and forwards it to another interface according to the destination address of the data packet. To put it bluntly, routing is a process of collecting and distributing data. In front-end development, the role of routing is mainly to ensure the synchronization of view and URL information. Users can change the URL by manually entering or interacting with the page, and then the program sends a request to the server to obtain resources, and then redraws the UI. The iOS mobile terminal can borrow the front-end ideas, follow the agreed routing protocol, and map to specific view components/controllers/functions and other resources by some means.

Mobile (iOS) routing

URI

Uniform Resource Identifier (Uniform Resource Identifier) ​​is a string used to identify Internet resources, which allows for interoperability of resources in the network through a specific protocol. The most common form of URI is the Uniform Resource Locator URL.

URL 这是一段URL,每一段都代表类对应的含义,我们可以理解 URL 为一段携带了获取到某资源的所有必须的信息的特定组合字符串。

URL Scheme

iOS 系统里面支持的 URL Scheme 方式打开应用。我们可以通过 TARGETS -> Info -> URL Types 添加应用的 Scheme,该 Scheme 可以理解为 App 的一个身份标识,用它可以打开我们的应用(在三方分享时经常需要我们去平台生成自己的 Scheme,三方平台用此字段跳转我们App)。 insert image description here 这样,我们运行App之后,通过 Safari 就可以跳转到我们的App。输入 csdnapp:// 跳转。

你也可以通过 info.plist 文件中的 URL types 字段管理你的 URL Scheme 信息。即使你没有用过 URL Scheme,那么你一定使用过一些系统的服务,例如拨打电话,使用系统邮箱等功能。他们的协议头就像下面这样:

mailto://
tel://110
复制代码

如果你未使用过,那么最直观的,你在手机的系统浏览器 Safari 中输入上面的命令,系统就会提示你是否拨打电话或者编写邮件。tel://mailto:// 就是系统电话和邮件应用的路由协议头。

一些热门的应用同样定义了自己的路由协议头,例如QQ、微信、微博等:

mqq://
weixin://
sinaweibo://
复制代码
接收处理

通过 -openURL: 过来的的消息,我们可以通过 AppDelegate 中的回调代理进行接收处理:

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options
复制代码

Universal Links

使用 URL Scheme 有两个弊端,第一个就是混乱,由于 URL Scheme 是自定义字段,任何App都可以使用 weixin:// 这就可能导致系统跳转错误,这种情况在公司开发一系列的应用时经常发生。第二个就是如果用户未安装与URL Scheme对应的应用时,系统则无法正常跳转,这时通常需要我们程序员手动判断是否可以打开此 URL : -canOpenURL:,然后引导用户去安装对应的应用。

在 iOS 9.0 之后,苹果新增了一项功能 Universal Links,直译就是通用链接,这个功能让我们可以通过普通的 HTTP 链接就能启动我们的 App。

使用 Universal Links 跳转应用的好处就是:

如果安装了App,无论是在系统浏览器 Safari 里,还是在其他使用了webView控件的页面中,都可以打开App。

如果没有安装App,就会打开对应的网页,这个网页可以是宣传官网,又或者是下载安装地址。

从iOS 系统里面支持的URL Scheme方式,我们可以看出,对于一个资源的访问,苹果也是用URI的方式来访问的。

解决了什么问题

insert image description here 借用iOS 组件化 —— 路由设计思路分析一文中的图,很形象的展示了项目中各个控制器模块之间错综复杂的关系。 我们项目中使用的路由组件是基于MGJRouter进行二次开发实现的路由组件CNRouter,各种页面之间的跳转都可以通过 URL 的方式进行路由, 使用Router之后大概是这样: iOS routing

步骤
  • APP内每个页面控制器都需要对应一个路由地址URL
  • 路由页面跳转前,需要将对应的路由地址URL注册到CNRouter
  • 不同页面跳转时,将消息发送到CNRouter中统一处理
  • CNRouter根据其注册的URL来找到对应信息,然后负责实例化,解析参数,跳转页面等业务处理

通过上述步骤,我们可以看到,每个控制器之间并不需要相互依赖对方,可以完美的解决不同模块之间的耦合。

动态路由的价值

基于CNRouter URL跳转,我们又实现了动态路由功能,它的职责主要有:

  • 承担 App 内所有页面跳转逻辑
  • 通过Apollo配置,支持获取/配置路由替换规则
  • 匹配所有的路由跳转规则,命中路由规则的直接进行跳转
  • 将实际跳转目标地址传递给路由组件执行实际的跳转行为(路由重定向)

dynamic routing

实现方案

1、 路由注册与跳转

整个CNRouter就是由一个NSMutableDictonary *routes 控制的,routes中存放了所有已经注册的URL。

@interface CNRouter ()
/**
 *  保存了所有已注册的 URL
 *  结构类似 @{@"blog": @{@":blogId": {@"_", [block copy]}}}
 */
@property (nonatomic) NSMutableDictionary *routes;
@end
复制代码

insert image description here 通过以下方法构造路由匹配规则的字典。

- (NSMutableDictionary *)addURLPattern:(NSString *)URLPattern
{
    NSArray *pathComponents = [self pathComponentsFromURL:URLPattern];

    NSMutableDictionary* subRoutes = self.routes;
    
    for (NSString* pathComponent in pathComponents) {
        if (![subRoutes objectForKey:pathComponent]) {
            subRoutes[pathComponent] = [[NSMutableDictionary alloc] init];
        }
        subRoutes = subRoutes[pathComponent];
    }
    return subRoutes;
}
复制代码

例如注册以下两条路由:

[CNRouter registerURLPattern:@"https://blog.csdn.net/:userName/article/details/:articleId"  toHandler:^(NSDictionary *routerParameters) {
    
}];
[CNRouter registerURLPattern:@"csdnapp://app.csdn.net/blog/detail"  toHandler:^(NSDictionary *routerParameters) {
    
}];
复制代码

按照上面构造路由匹配规则的字典的方法,该路由规则字典就会变成这个样子:

{
    https = {
        blog.csdn.net = {
            :userName = {
                article = {
                    details = {
                        :articleId = {
                            _ = < __NSMallocBlock__: 0x282240ac0 > ;
                        };
                    };
                };
            };
        };
    };
    csdnapp = {
        app.csdn.net = {
            blog = {
                detail = {
                    _ = < __NSMallocBlock__: 0x282241bc0 > ;
                };
            };
        };
    };
}
复制代码

路由规则字典生成之后,等到路由匹配的时候就会遍历这个字典。所有注册的路由,都是以这种方式存放在routes中。 当有路由跳转时,调用以下方法:

[CNRouter openURL:@"https://blog.csdn.net/weixin_36162680/article/details/123161859" withUserInfo:@{@"isLogin": @YES} completion:^(id result) {
    NSLog(@"result = %@",result);
}];
复制代码

上面的URL会匹配成功,那么生成的参数字典结构如下:

{
	userName = "weixin_36162680";
	CNRouterParameterUserInfo = {
		isLogin = 1;
	};
	CNRouterParameterURL = "https://blog.csdn.net/weixin_36162680/article/details/123161859";
	articleId = "123161859";
}
复制代码

由上可见,路由组件CNRouter,它主要负责:

  • 启动时注册路由和页面
  • 查询正确的页面进行跳转或者执行其他业务逻辑

route registration route jump

2、 动态路由与规则配置

概念

动态路由:粗略的讲就是指你的URL地址与页面或者组件之间的映射关系。 本项目会根据Apollo配置,通过远端下发的方式,去动态构建这个路由映射表。实现动态显示可跳转的页面或组件。

路由重定向

对于移动端的路由重定向,实际上就是将一个路由转换为另一个路由,例如: https://live.csdn.net/room/:id 转换为: csdnapp://app.csdn.net/live/room?id=xxxx

规则配置

路由重定向中的一个关键节点就是“配置”,我们需要一个路由规则列表来记录和下发匹配规则。为了方便下发路由规则表,我们将这份配置表存放在Apollo,动态地下发到客户端。

A routing rule is divided into a Key and the corresponding Value. The Key is the route to be registered (matching rule), and the Value is the name of the controller to be instantiated or the URL to be redirected, which is defined in JSON format. E.g:

{
    "https://blog.csdn.net/:userName/article/details/:articleId": {
        "class": "CNNewBlogDetailViewController"
    },
    
    "https://live.csdn.net/room/:id": {
        "redirectUrl": "csdnapp://app.csdn.net/live/room"
    }
}
复制代码

process

Pull the correct routing table (mapping rule) in the APP startup phase, register and save it.insert image description here

In the actual code implementation, we judge whether there is a redirection route before the page jumps, and if there is a redirection route, execute the redirection jump, otherwise directly initialize the target controller. route registration route jumpThe code is implemented as follows:

[CNRouter cn_addRoute:router handler:^(NSDictionary *routerParameters) {
    //判断是否有重定向
    if ([routerParameters[kCNRouteClassRedirectUrl] isValid]) {
        //跳转重定向地址
        [CNRouter cn_openURL: routerParameters[kCNRouteClassRedirectUrl] withUserInfo: routerParameters];
    }else{
        //初始化控制器
        [self executeRouterClassName:className parameters: routerParameters];
    }
}];
复制代码

practical application

First, let's talk about the background of using dynamic routing. The skill tree module in the CSDN APP is developed with a small program. Now you need to open the PC URL in the APP without H5, and you need to use a small program to display the page. The URL of the APP applet is inconsistent with the URL of the PC. If the PC URL is used directly, there will be a problem that the applet cannot be redirected. Take the skill tree jump in private messages as an example:

  • Routing URL for skill tree applet in iOS:csdnapp://app.csdn.net/mpTinyApp?id=__UNI__C92EAF9?language=cloud_native
  • The URL sent in the PC private message: https://edu.csdn.net/skill/cloud_native

So to solve this problem, you only need to add a rule to the dynamic routing of iOS https://edu.csdn.net/skill/cloud_nativeand replace dynamic with csdnapp://app.csdn.net/mpTinyApp?id=__UNI__C92EAF9&language=cloud_native. The configuration is as follows:

{
    "https://edu.csdn.net/skill/:language": {
        "redirectUrl": "csdnapp://app.csdn.net/mpTinyApp?id=__UNI__C92EAF9"
    }
}
复制代码

insert image description here

Guess you like

Origin juejin.im/post/7087875341460439071