另辟蹊径--极简Swifty路由

另辟蹊径--极简Swifty路由

1. 前言

在组件化通讯方案的设计之初,尽管咱们是纯Swift的组件化,我也一直难逃窠臼的想用注册(不管是注册协议仍是注册URL)的方式来解决问题,或者采用CTMediatorTarget-Action方式,具体几种组件化方案的实现与利弊见文章:iOS 组件化 —— 路由设计思路分析🔥🔥 git

2. 弯路(经验)

最开始设计的组件化解决方案,由于做为一个电商项目(才不是这个缘由),因此我仅采用了URL注册的方式,我一直力求的它应该具有的特性以下:github

  1. 组件解耦
  2. 能够方便的跳转到任何已注册的页面
  3. 不要硬编码
  4. 模块(组件)可获取App生命周期
  5. 模块(组件)对URL的注册不须要手动调用
  6. 可能的话,实现3端同样的跳转逻辑
  7. 支持动态下发,如此即支持简单的热修复

其实34,已经跳出了组件路由设计的范畴。确切的说应该是模块解耦的范畴。swift

2.1 实现

我把router设计成单例,目的是保证其持有的["String": func]的字典的惟一肯定性,其中func做为闭包形式,传入参数返回ViewController。那么注册环节就显而易见的为register(_ key: String, value: func),调用就会根据key,执行闭包func返回ViewController,以此解决1api

在对其中key的设计使用上,由于注册方与调用方都会用到,因此咱们将其写在公共组件内,又由于key会附带传一些简单的值,因此我又加了一个方法对key进行赋值处理操做,目的是为了保证第3条。bash

在模块解耦问题的处理上,我设计了一个继承AppDelegate方法的协议,暂称为AppLifeCycle,同时添加了一些方法用于初始化注册操做。再又设计了一个脚本,能够将遵循AppLifeCycle的实例生成一个plist文件,这样在App启动时候,一个方法调用就实现全部路由注册功能,以此解决45服务器

对于第7点,在设计之初由于公司尚未服务器端动态下发的功能,因此又加了中间件作fallBack处理(固然也都没用上)。闭包

3. Swifty组件化

虽然原有的路由设计与模块解耦方案已经支持现阶段业务需求,可是使用上过于复杂,不够友好,并且也没用上多少swift的特性,反而这些实现,若是用Objective-c实现起来,会更方便一些,好比脚本生成plistOC均可以不须要。app

最近有同事在对路由作抽离精简,仅抽出router部分,主要在接口设计上进行优化。我在看完后对一些功能点提了优化可能,后续一直的交流沟经过程中,忽然想到,我能够用Protocol Witness Table来实现这个路由啊!ide

其原理是: swift会维护一个Protocol Witness Table, 此表会保存实现了protocol协议的方法的指针地址,当咱们调用方法时,是经过获取对象的内存地址和方法的位移去查找的。组件化

因此咱们能够用一个协议定义入参,一个协议定义实现,同一个Enum(建议使用的)去实现,便可实现功能。

这种方式相似于target-action,无需注册,接口约定,还具备其余一些优势:

  1. api接口及其简单,上手难度0
  2. 接口能够统一在一个库内,须要的支持库也变少了
  3. 无硬编码

那么如此,咱们的路由设计的核心代码,以下:

public protocol MediatorTargetType {} // 用于接口定义,约束接口

public protocol MediatorSourceType {  // 用于枚举实现
    var viewController: UIViewController? { get }
}

复制代码

target须要遵循的协议就这么些。

mediator须要遵循的协议与实现:

public protocol SwiftyMediatorType {
    func viewController(of target: MediatorTargetType) -> UIViewController?
}

extension SwiftyMediator: SwiftyMediatorType {
    public func viewController(of target: MediatorTargetType) -> UIViewController? {
        guard let t = target as? MediatorSourceType else {
            print("MEDIATOR WARNINIG: \(target) does not conform to MediatorSourceType")
            return nil
        }
        guard let viewController = t.viewController else { return nil }
        return viewController
    }
}

复制代码

以上便是核心代码。 经过接口收束,须要传入MediatorTargetType,尝试转换成目标类型MediatorSourceType,以此返回viewController

4. 使用

在使用中,咱们仍然须要一个公共的组件库,对路由目标进行定义。假设这个库叫MediatorTargets,其内容以下:

public enum ModuleAMediatorType: MediatorTargetType {
    case home(title: String)
    case personal(color: UIColor)
}

复制代码

而后在咱们写的模块库中,此时咱们是路由目标的提供方,如3中核心代码所示,咱们须要 让ModuleAMediatorType再遵循协议MediatorSourceType,以此支持ModuleAMediatorType返回viewController

import SwiftyMediator
import MediatorTargets

extension ModuleAMediatorType: MediatorSourceType {
    public var viewController: UIViewController? {
        switch self {
        case .home(let title):
            let vc = UIViewController()
            vc.view.backgroundColor = .green 
            vc.title = title
            return vc
            
        case .personal(let color):
            let vc = PresentedViewController()
            vc.view.backgroundColor = color
            vc.title = "Presented"
            return vc
        }
    }
}
复制代码

那么实现方的调用,只须要:

import MediatorTargets
import SwiftyMediator

let vc = Mediator.viewController(of: ModuleAMediatorType.home(title: "Home"))

复制代码

嗯,就是这么简单。

若是只作简单的模块间通讯,到这是足够的了, 主要的就是2个协议。

5. 路由化及动态化

固然,有些时候咱们须要作一些动态化的路由策略,好比作一下动态路由下发。我也对SwiftyMediator作了一些接口适配,使用方式以下:

  1. 先将须要路由动态化的已遵循MediatorTargetType的协议ModuleAMediatorType,继续遵循协议MediatorRoutable,并实现协议:
extension ModuleAMediatorType: MediatorRoutable {
    public init?(url: URLConvertible) {
        switch url.pattern {
        case "sy://push":
            self = .push(title: url.queryParameters["title"] ?? "default")
        case "sy://present":
            self = .present(color: UIColor.red)
        default:
            return nil 
        }
    }
}
复制代码
  1. 调用SwiftyMediatorfunc register(_ targetType: MediatorRoutable.Type),注册ModuleAMediatorType
  2. 可选:如须要替换某个路由指向,调用SwiftyMediatorfunc replace(url: URLConvertible, with replacer: URLConvertible)方法便可
  3. 使用url的方式作路由:Mediator.push("sy://push?title=hahaha")

当须要实现动态化的时候,不可避免的要去注册,并且要实现协议中的枚举初始化。虽然有些不便,可是在总体的接口收束度上仍是挺不错的。相比较注册URL的方式来讲,这些注册就少不少了。

6. 模块获取App生命周期

鉴于目前系统有比较全面的生命周期通知定义,并且不须要在模块中大量注册url,因此这部分功能目前在考虑是否须要添加。


虽然代码很简单,实现也很简单,可是跳出惯性思惟,再去尝试一样须要不少思考。

SwiftyMediator,欢迎star。

其余使用方法见:

demo

参考资料:

WWDC - Protocol Witness Table

swift的witness table

URLNavigator

相关文章
相关标签/搜索