2023-02-26 今天难得的一个好天气,一整天阳光暖洋洋,精力十足,整理框架时,对路由一些小想法,于是花了一整天,把整个路由组件做成了pod,抽了出来.
上一篇路由讲了个当时随手写的一个框架,但写完后项目没怎么用,后面因为事情太多,渐渐也就忘了,这次重新整理了一下思路,对路由重新设计了一番.
架构
|____Classes
| |____GetRouter.swift
| |____GetRouterHandlerSource.swift
| |____.gitkeep
| |____ReplaceMe.swift
| |____GetRouterMiddleware.swift
| |____GetRouterName.swift
| |____GetRouterHandler.swift
|____Assets
| |____.gitkeep
路由设计
路由分远程路由和本地路由,我们需要做的是统一,远程路由适用与banner跳转,js交互操作等等情况,一般来说是string执行的路由跳转,本地路由需要规定要,一个页面的跳转,最好走唯一的路由,好处就是封装,低耦合,A页面理论上是不应该import B页面的,还有就是埋点参数上报等等,统一路径.
流程
- 远程路由
urlStirng->encoding->parse解析路由,合并参数->匹配本地路由->执行跳转 - 本地路由
匹配本地路由->执行跳转
GetRouter
远程路由与本地路由解析的流程文件后,解析完成后交给GetRougerHandler执行
GetRougerHandler
匹配路由名相同的GetPage 执行中间件拦截, 执行跳转
GetRouterMiddleware中间件拦截
中间件用于公共拦截方式,举例说明,我们如果要想跳转vip直播间,我们需要前置判断当前登录者是不是vip,当前vip直播间是否正在直播,一个是同步就可以判断出当前用户vip状态,一个需要实时请求后端直播状态接口等.
传统方式中,我们可能需要通用写个判断
// 判断vip
guard User.isVip else {
return
}
// 判断直播状态
RequestLiveStatus {
jumpLive()
}
这样写不太优雅, 所以我增加了同步拦截与异步拦截(注意,中间件的环境在iOS13以上处于异步线程,执行UI操作,吐丝重定向等请回到主线程执行)
open class GetRouterMiddleware {
/// 优先级
let priority: Int
public init(priority: Int) {
self.priority = priority
}
/// 处理返回结果
/// - Returns: true: 允许通过 false: 禁止
open func handler(routeName: GetRouterName, params: GetDict?) -> Bool { true }
@available(iOS 13.0.0, *)
/// 异步中间件, 注意如果想要在此出重定向路由,注意要回到mainThread
/// - Parameter routeName: 路由名
/// - Returns: true: 允许通过 false: 禁止
open func handlerAsync(routeName: GetRouterName, params: GetDict?) async throws -> Bool { true }
}
这里注意一下,异步拦截是iOS13以上可用,需要自行构造一个async任务(相关只是可以参考await async 构造async),demo也有相关示例,使用起来也很方便
GetPage
注册路由时包装的路由匹配信息,包括中间件和行为.
GetRouterHandlerSource
提供GetPage的source来源
GetRouterName路由名设计
原先我们设计的路由名是这样的
enum GetRouterName: String {
// MARK: - --------------------------------------公共
case http = "http"
case https = "https"
// MARK: - --------------------------------------发现
// MARK: - --------------------------------------社区
case community_post_detail = "community_post_detail"
// MARK: - --------------------------------------个人
// MARK: - --------------------------------------我的
case mine_setting = "mine_setting"
case mine_complete = "mine_complete"
// MARK: - --------------------------------------帖子
case post_detail = "post_detail"
}
很简单,很易懂,但是有缺点
- enum可以在switch中可以枚举掉所有的选项,保证我们不会漏掉任何路由名,但是,enum无法写成extension,也就是说,我们必须把所有名卸载一个文件里,这样对模块化是不好的,我希望使用的场景是我每个模块去注册这个路由,然后提供我模块的模块名和路由处理选项,这样就可以做到单独开发自己的选项
- string不是类型,在本地调用中,我们不可避免的会出现必须强制带类名的方式,写起来就不是那么清爽,例如
Router.to(GetRouterName.mine_setting)
,我想要的是Router.to(.mine_setting)
所以,利用swift的语法特点,我修改成这种写法
/// 路由列表容器
public struct GetRouterNames {}
/// 路由列表
public struct GetRouterName: RawRepresentable,
ExpressibleByStringLiteral,
Hashable {
public var rawValue: String
public init(rawValue: String) {
self.rawValue = rawValue
}
public init(stringLiteral value: StringLiteralType) {
self.rawValue = value
}
public var hashValue: Int {
rawValue.hashValue
}
}
这里写了一个类型GetRouterName,利用swift字面量,可以达到这种定义路由名的效果
// GetRouter+Home.swift
// quick
//
// Created by suyikun on 2023/2/25.
//
import Foundation
extension GetRouterNames {
/// 发现列表
static let discover: GetRouterName = "/discover/list"
/// 发现页详情
static let discoverDetail: GetRouterName = "/discover/detail"
}
虽然,我们损失了enum强制枚举所有枚举的效果,但是,我们收获了模块化路由名+路由名类型的优点
使用示例
注意事项: 获取参数时,远程路由获取的value是string类型,需要在写代码时考虑兼容转换
自己在业务跳转方面,做好兼容即可, 例如下图这样处理PS
在我看来,路由设计应该是可以纯粹的解耦合的,他跟业务的跳转没有一点关系,只是一些规则的解析,
总结一下,回顾了下整个路由设计,一天的收获满满,如果你们喜欢我的文章,或者对路由设计有什么批评建议意见,希望可以点赞留言
最后留一下git地址: 戳这前往
网友评论