Skip to content

进阶教程

Rakuyo edited this page Apr 17, 2020 · 4 revisions

本教程将详细介绍 RaRouter 的组成,以及如何基于 RaRouter 自定义路由。

本文基本不涉及 快速入门 的内容,建议先阅读快速入门,再阅读本教程。

面向协议

RaRouter 采用 面向协议 的编程思想,核心内容由四个协议 + 一个类组成:

  • ModuleRouter 协议:用于定义模块。
  • RouterTableProtocol 协议:用于定义路由表。
  • RaRouter 协议:提供执行、注册路由的能力。
  • RouterRegister 协议:用于简化路由注册逻辑。
  • RouterFactory 类:内部使用 [String : block] 存储注册过的路由。

定义模块

定义模块时,需要同时使用 ModuleRouterRouterTableProtocol 协议。

ModuleRouter

public protocol ModuleRouter {
    
    associatedtype Table: RouterTableProtocol
}

ModuleRouter 协议用来定义模块,只要是遵守该协议的类型,RaRouter 就会认为它是一个 模块

协议要求一个 RouterTableProtocol 类型的泛型,意味着这个模块既然需要路由,那么它就必须提供一个 路由表。至于这个泛型实质上的用途,将在 执行路由 中详细介绍。

RouterTableProtocol

public protocol RouterTableProtocol: Codable {
    
    var url: String { get }
}

RaRouter 内部使用 [String : block] 存储路由(详细参考注册路由部分),Key 即为路由字符串,所以在定义模块时,模块的路由表是逃不开的东西。

为了避免路由字符串散乱存放、复制错误以及拼写错误等问题的发生,RaRouter 使用 RouterTableProtocol 协议来提供、封装硬编码的路由字符串。

协议要求的 url 属性即是供内部使用,用来读取路由地址的。

注册路由

RaRouter 使用 RouterFactoryRaRouter 以及 RouterRegister 共同完成路由的注册逻辑。

RouterFactory

public class RouterFactory {
    
    public static let shared = RouterFactory()
    
    private init() {}
    
    public lazy var doHandlerFactories: [String : DoHandlerFactory]  = [:]
    
    public lazy var resultHandlerFactories: [String : ResultHandlerFactory]  = [:]
    
    public lazy var viewControllerHandlerFactories: [String : ViewControllerHandlerFactory]  = [:]
}

RouterFactory 是一个 单例 类,其没有继承任何的父类。内部使用三个字典属性存储默认提供的三种路由操作。

路由的存取本质上就是对字典属性的读取操作。

RouterRegister

public protocol RouterRegister: class {
    
    static func register()
}

RaRouter 的早期版本中,需要对每个模块单独执行注册操作,在模块数量较多的情况下代码显得十分“冗余”且不易维护、扩展。每新依赖一个组件,就需要添加对应的注册方法;删除组件时则需要删除对应的注册方法,十分麻烦。

RouterRegister 协议就是用来简化上述注册流程的一部分。

RouterRegister 协议要求遵守对象必须是一个类。这样,在程序启动时,我们就可以获取所有类列表,遍历判断其是否遵循 RouterRegister,进而执行协议要求的 register() 方法,执行路由的注册代码。

这些操作被封装成 initialize() 方法,定义在了 RaRouter 协议的扩展中,具体的代码可以查看:Register Module

经过一系列的封装,现在我们只需要一行代码就可以完成路由的注册,并且依赖、移除模块时,不需要对注册操作有任何额外的修改

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
    Router<Modules>.initialize()
        
    return true
}

RaRouter

RaRouter 协议是 RaRouter 组件的 “万金油”。其完整协议参考:执行路由

在路由的注册过程中,该协议担任了两个任务:

  1. RouterFactory 单例写入数据。
  2. 提供 initialize() 方法,简化注册流程(在 RouterRegister 已经提及)。

我们在 Router+Register.swift 文件中,对 RaRouter 协议进行扩展,其中提供了向 RouterFactory 单例写入数据的方法:

// MARK: - Register With String URL

public extension RaRouter {
    
    static func register(for url: String, _ factory: @escaping DoHandlerFactory) {
        RouterFactory.shared.doHandlerFactories[url] = factory
    }
    
    static func register(for url: String, _ factory: @escaping ResultHandlerFactory) {
        RouterFactory.shared.resultHandlerFactories[url] = factory
    }
    
    static func register(for url: String, _ factory: @escaping ViewControllerHandlerFactory) {
        RouterFactory.shared.viewControllerHandlerFactories[url] = factory
    }
}

执行路由

执行路由也是 RaRouter 协议的主场。其完整定义如下所示:

public protocol RaRouter {

    associatedtype Module: ModuleRouter
}

协议要求了一个 ModuleRouter 类型的泛型,其有两个用途:

  1. 通过限制 Module 的具体类型,方便封装模块所提供的操作。
  2. 简化路由的执行、注册逻辑。

1.

关于第一点,已经在 定义模块 中有所提及,在扩展 RaRouter 协议封装方法时,使用 where 关键字限制 Module 的具体类型,可以达到 命名空间 的效果,避免在执行路由时,自动补全过多,不方便查找 以及 方法重名等问题的发生。

2.

基于该泛型的存在,RaRouter 进一步封装了执行、注册路由的方法,修改 table 参数为 Module.Table 类型,使其可以直接调用 ModuleRouter.Table 中封装的枚举值作为路由地址。

getResult 操作为例:

public extension RaRouter {

    static func getResult(_ table: Module.Table, param: Any? = nil) throws -> Any? {
        guard let factory = RouterFactory.shared.resultHandlerFactories[table.url] else {
            throw RouterError.notHandler(url: url)
        }

        return try getResult(table.url, param: param)
    }

    static func register(for table: Module.Table, _ factory: @escaping ResultHandlerFactory) {
        RouterFactory.shared.resultHandlerFactories[table.url] = factory
    }
}

同时,项目中还保留了直接使用 String 类型注册、执行路由的能力,方便通过接口JSON 文件等方式动态执行、注册路由。

Router

RaRouter 中默认提供了一个遵守 RaRouter 协议的类型:Router,其声明如下所示:

public enum Router<Module: ModuleRouter>: RaRouter { }

如无特殊需要,可以直接在该类型的基础上编写路由相关内容。

Global

RaRouter 中还提供了一个 Global 类型。其遵守 ModuleRouter 协议,可理解为一个全局模块。其声明如下所示:

public enum Global: ModuleRouter {
    
    public typealias Table = RouterTable
    
    public enum RouterTable: String, RouterTableProtocol {
        
        public var url: String { rawValue }
        
        case none = "mbc://global/none"
    }
}

该类型提供了在 无法使用具体模块类型调用 RaRouter 协议 的场景下,执行、注册某些路由操作的能力。

一般用来配合接口JSON 文件等方式动态执行、注册路由。

扩展路由

基于面向协议的高扩展性,您可以定义自己的 Router 类型,或扩展 RaRouter,提供更多种类的操作。

代替 Router

在某些情况下,您可能不需要组件自带的 Router 类型,或想将其定义为一个类。

只需要遵守 RaRouter 协议,您便可以声明自己的 Router 类型。

扩展更多操作

您还可以进一步扩展 RaRouter 协议。假如您的项目的模块中,经常定义某些返回 String 类型的方法,您便可以在 getResult 定义自己的 string() 方法:毕竟 viewController 操作也是在 getResult 的基础上进一步封装的。

您还可以定义自己的 RouterFactory 类型,提供对应于操作的字典属性:或者不用字典来存储

随心所欲

在自定义路由时,请牢记 “随心所欲”。

do 操作也好,viewController 操作也罢,无外乎就是在 RaRouter.getResult() 方法之上的一层封装。

RaRouter.getResult() 本质上也只是在操作 RouterFactory 中的字典属性。

一定要用字典来存储路由的映射关系吗?或许还有其他的办法。

您可以基于 RaRouter 进行简单的封装、改造。如果 RaRouter 能成为一个引子,帮助您构造出更符合您使用习惯、性能更高的路由组件,也是极好的。