当前位置: 首页 > 工具软件 > Swifty > 使用案例 >

另辟蹊径--极简Swifty路由

廖华翰
2023-12-01

另辟蹊径--极简Swifty路由

1. 前言

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

2. 弯路(经验)

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

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

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

2.1 实现

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

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

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

对于第7点,在设计之初因为公司还没有服务器端动态下发的功能,所以又加了中间件做fallBack处理(当然也都没用上)。

3. Swifty组件化

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

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

其原理是: 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

转载于:https://juejin.im/post/5c43070e6fb9a049a5713435

 类似资料: