另辟蹊径--极简Swifty路由

news/2025/2/23 22:03:24

另辟蹊径--极简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


http://www.niftyadmin.cn/n/4441538.html

相关文章

Wine 0.9.20 发布

Wine 是在 Linux 操作系统下执行部分 Windows 应用程序的工具!如果你想在 Linux 下运行 Windows 程序,Wine 将是你必不可少的工具!Wine Is Not Emulator在 X 和 UNIX 之上的,Windows 3.x 和 Windows APIs 的实现.它是一个Windows 兼容层,这个层即提供了一个用来从 Windows 源进…

emotion使用笔记

emotion 9 升级到 10 了,所以9的用法就不总结了,官网上介绍的很详细。 点击传送门查看9升到10都变了哪些。 几点建议: (1)10中尽量只用import styled from emotion/styled/macro 原因是调试方便,可以方便地…

安装weblogic 11g

参考 https://blog.csdn.net/z69183787/article/details/38401013 https://blog.csdn.net/wjf8882300/article/details/52303728 http://blog.chinaunix.net/uid-29226528-id-4716909.html https://www.linuxidc.com/Linux/2013-09/89834.htm https://blog.csdn.net/chs007chs/…

sqldps病毒的手动删除办法

该病毒会首先探测对方机器的SQL服务端口,并会探测其他机器的其他服务端口,占用网络资源网络该病毒的主要症状有:1、网络可以ping的通,但是不能正常上网,打开网页有时会打不开。2、打印机不能共享,本机可以打…

note_vue-cli搭建项目

vue init webpack projectName# install dependencies npm install //安装依赖# serve with hot reload at localhost:8080 npm run dev //开发模式# build for production with minification npm run build //打包模式转载于:https://www.cnblogs.com/lsy-19…

Redis 高频面试题 2023 最新版

Redis 高频面试题 2023 最新版 文章目录 Redis 高频面试题 2023 最新版一、Redis缓存相关1. 什么是缓存穿透?如何解决2. 什么是缓存击穿?如何解决3. 什么是缓存雪崩?如何解决 一、Redis缓存相关 1. 什么是缓存穿透?如何解决 是什…

Solaris管理员常用168条命令简明手册(转)

Solaris系统命令中英对照(很长也较全)A ab2admin—对AnswerBook2进行管理的命令行界面 ab2cd—从Documentation CD中运行AnswerBook2服务器 ab2regsvr—向联合域名服务注册AnswerBook2文档服务器 accept、reject—接受或拒绝打印请求 acct—对计数及各种…

基础链表翻转操作

# 对于带头结点的单链表存在两种的翻转操作# 基本构造如下 typedef int ElementType; typedef struct LNode *PtrToLNode; typedef PtrToLNode Position; typedef PtrToLNode List; struct LNode {ElementType Data;PtrToLNode Next; }; 将a1到an元素再依次以头插入的方式生成…