Swift

iOS(Swift) 路由设计

2022-02-16  本文已影响0人  简单coder

2022-02-17 更新url 编码

最近在整理 flutter 面试的内容,顺便也回顾swift 的框架,面试题等等,然后就看到了我以前封装的 Get系列,还记得我之前提过一个 MVP模式微变种的文章吗,那个是模仿flutter Get框架写的状态管理框架,Get 另外一个好用的地方在于它的路由框架写的很好,所以我同时我另外其实也了一个 iOS 的路由框架,不过没有参照Get 的思想,只不过也归到我定义的Get框架中去了,这里顺便也抛出来

设计思路

代码分析

拋出去给外部的方法,这里我暂时没有承担掉 pop 的职责,原因只是因为懒,后续肯定需要做的.back,backToRoot.removeClass等等.

///  注册路由处理
    static func registerHandler(_ handler: GetRouterHandlerDelegate) {
        to.handler = handler
    }

注册其实就是抛出去路由给业务处理,这个等会儿再说,先从路由解析开始

外部调用

假设banner 的点击事件是

GetRouter.router(name: "native://www.taoqu.com?ios_path=/edit_info&userId=200944533&targetId=222222222", arguments: nil)

这个路径是符合我们 app 业务路径的,为什么我没有用url中的 path 来区分跳转路径呢,因为有可能安卓路径设计跟iOS 是不一样的,所以我们用固定参数 ios_path 来取跳转路径,参数名自己设计即可,这里的路径可以设计成全部是一级路径,或者一级+二级路径,这看你们自己想怎么设计解析的方案都行.
scheme 和 host 可以用作权限验证,非 native:// 和 www.taoqu.com 的拒绝跳转,另外也可以另外增加 http://和 https://协议的识别跳转

路由参数解析

    /// 路由方法
    static func router(name: String?, arguments: Dict?) {
        guard let name = name else { return }
        if let urlComponents = URLComponents(string: name) {
            // url 路由,包括协议 native, http, https 比如 native://www.taoqu.com?ios_path=/mine/setting&userId=200944533
            to._parse(urlComponents: urlComponents, argument: arguments)
        } else {
            // 本地路由, 比如 name=/mine/setting
            to.handler?.handler(path: name, arguments: arguments)
        }
    }

这里我只做了一件事,就是不管远程路由和本地路由,参数都整合到一个 map 中.然后继续

执行路由

private func _doRouter(urlComponents: URLComponents, argument: Dict?) {
        guard let scheme = urlComponents.scheme else {
            return
        }
        switch scheme {
        case GetRouterConfig.native.scheme:
            guard let ios_path = argument?["ios_path"] as? String else { return }
            handler?.handler(path: ios_path, arguments: argument)
        case GetRouterConfig.http.scheme, GetRouterConfig.https.scheme:
            handler?.handler(path: "http", arguments: argument)
        default:
            getllog("协议头错误")
        }
    }

抛给业务解析路由

我们 GetRouter 框架细看也只是做了一点事儿,就是把 urlString 中的参数和 本地argument参数整合到了一起,然后分出路径,抛出给业务.
这里就讲到业务路由如何解析了,原本我是想着按模块区分路径的,比如个人模块自己管理自己的 class,甚至直接用 enum 来匹配路径,这样很Swift,但是有问题,1.我无法一次性全部告诉web 端我所实现的所有路由,2.我无法实现一二级路径匹配,这个问题还好,我自己做路径分隔,然后按模块填入,这样也能实现 enum 匹配路径,但是这更增加了1的难度,就像下面这样

/// 公共路由
struct RouterGeneralHandler: GetRouterHandlerDelegate {
    enum RouterGeneralEnum: String {
        case http,https
    }
    func handler(path: String, arguments: Dict?) -> Bool {
        switch path {
        case RouterGeneralEnum.http.rawValue:
            getllog("处理了")
        default:
            return false
        }
        return true
    }
}

/// 个人模块路由
struct RouterPersonHandler: GetRouterHandlerDelegate {
    enum RouterPersonEnum: String {
        case edit_info
        case setting
    }
    func handler(path: String, arguments: Dict?) -> Bool {
        switch path {
        case RouterPersonEnum.edit_info.rawValue:
            getllog(arguments?.toJSONString())
            CompleteInfoVC.push()
        default:
            return false
        }
        return true
    }
}

所以, 最后决定,业务层路由解析修改成这样

业务路由解析

// 路由名管理,这个类可以直接复制给 web,相当于所有已维护的路由表
enum GetRouterName: String {
    // MARK: - --------------------------------------公共
    case http = "http"
    case https = "https"
    // MARK: - --------------------------------------发现
    
    // MARK: - --------------------------------------我的
    case mine_setting = "mine_setting"
    // 路由名可用_也可用/,这个主要是强调统一规范
    case mine_complete = "/mine/complete"
    // MARK: - --------------------------------------帖子
    case post_detail = "post_detail"
}

路由解析


struct RougerHandler: GetRouterHandlerDelegate {
    let subHandlers: [GetRouterHandlerDelegate] = [
        RouterGeneralHandler(),
        RouterPersonHandler(),
    ]
    
    func handler(path: String, arguments: Dict?) -> Bool {
        for handler in subHandlers {
            if handler.handler(path: path, arguments: arguments) {
                return true
            } else {
                continue
            }
        }
        getllog("路由无法被解析")
        return false
    }
}


/// 公共路由
struct RouterGeneralHandler: GetRouterHandlerDelegate {
    func handler(path: String, arguments: Dict?) -> Bool {
        switch path {
        case GetRouterName.http.rawValue:
            getllog("处理了")
        default:
            return false
        }
        return true
    }
}

/// 个人模块路由
struct RouterPersonHandler: GetRouterHandlerDelegate {
    func handler(path: String, arguments: Dict?) -> Bool {
        switch path {
        case GetRouterName.mine_setting.rawValue:
            getllog("跳转个人设置")
            SettingVC.push()
        case GetRouterName.mine_complete.rawValue:
            getllog("跳转完善资料")
            CompleteInfoVC.push()
        default:
            return false
        }
        return true
    }
}

这块儿,就是由共同的业务开发一起维护,至于权限校验什么的,可以根据实际跳转的路径,具体的策略看你们自己想要怎么维护,加中间件可以在这里加.

参数解析

我在flutter 架构设计(基于 getx 的路由)中有写过一点

本地路由使用arguments还是parameters我有点忘了,但是这两种其中有一种肯定与上面的url 拼接参数方式取参的方式是一样的,直接可以用同一种方式取出来,之前的 demo 我测试过了,不过现在忘了,大家做个测试即可.

这里注意,对于带参的路由,由于远程路由解析下来的参数,key-value 的 value 是 string,也为了解决参数接收问题,我个人建议本地路由,传参也都使用 string 传参,这样,你就不需要在进行两种判断了,或者你可以手动判断类型,进行值的获取.虽然json 传参有可能会牺牲点编解码的性能. 这个东西见仁见智了,看你们自己判断.

我目前的 iOS 项目路由设计太过复杂了,为了兼容老版本路由,新版本路由,设计了很多策略,同事还另外写了一套本地路由,路由框架显得很复杂,所以这东西,在一开始,一定要定好规范,不要随意更改.
最后,贴一下主要的代码吧

//
//  GetRouter.swift
//  spsd
//
//  Created by suyikun on 2022/1/7.
//  Copyright © 2022 未来. All rights reserved.
//

import Foundation

class GetRouter {
    // MARK: - --------------------------------------singleton
    private static let to = GetRouter()
    private init() {}
    // MARK: - --------------------------------------property
    /// 路由处理
    private var handler: GetRouterHandlerDelegate?
    // MARK: - --------------------------------------func

    ///  注册路由处理
    static func registerHandler(_ handler: GetRouterHandlerDelegate) {
        to.handler = handler
    }

    /// 路由方法
    static func router(name: String?, arguments: Dict?) {
        guard let name = name else { return }
        if let urlComponents = URLComponents(string: name) {
            // url 路由,包括协议 native, http, https 比如 native://www.taoqu.com?ios_path=/mine/setting&userId=200944533
            to._parse(urlComponents: urlComponents, argument: arguments)
        } else {
            // 本地路由, 比如 name=/mine/setting
            to.handler?.handler(path: name, arguments: arguments)
        }
    }

    /// 路由解析, 将远程或者本地的路由整合到一个 Map 中
    /// - Parameters:
    ///   - name: 全路由
    ///   - argument: 参数
    private func _parse(urlComponents: URLComponents, argument: Dict?) {
        // 拼接参数,合并参数
        var dict = argument ?? Dict()
        if let queryItems = urlComponents.queryItems {
            for queryItem in queryItems {
                if let value = queryItem.value {
                    dict[queryItem.name] = value
                }
            }
        }
        /// 执行路由
        _doRouter(urlComponents: urlComponents, argument: dict)
    }

    private func _doRouter(urlComponents: URLComponents, argument: Dict?) {
        guard let scheme = urlComponents.scheme else {
            return
        }
        switch scheme {
        case GetRouterConfig.native.scheme:
            guard let ios_path = argument?["ios_path"] as? String else { return }
            handler?.handler(path: ios_path, arguments: argument)
        case GetRouterConfig.http.scheme, GetRouterConfig.https.scheme:
            handler?.handler(path: "http", arguments: argument)
        default:
            getllog("协议头错误")
        }
    }
}


struct RougerHandler: GetRouterHandlerDelegate {
    let subHandlers: [GetRouterHandlerDelegate] = [
        RouterGeneralHandler(),
        RouterPersonHandler(),
    ]

    func handler(path: String, arguments: Dict?) -> Bool {
        for handler in subHandlers {
            if handler.handler(path: path, arguments: arguments) {
                return true
            } else {
                continue
            }
        }
        getllog("路由无法被解析")
        return false
    }
}


/// 公共路由
struct RouterGeneralHandler: GetRouterHandlerDelegate {
    func handler(path: String, arguments: Dict?) -> Bool {
        switch path {
        case GetRouterName.http.rawValue:
            getllog("处理了")
        default:
            return false
        }
        return true
    }
}

/// 社区路由
struct RouterCommunityHandler: GetRouterHandlerDelegate {
    func handler(path: String, arguments: Dict?) -> Bool {
        switch path {
        case GetRouterName.community_post_detail.rawValue:
            getllog("跳转community_post_detail")
            if let postId = arguments?["postId"] as? String {
                PostDetailController(postId: postId).push()
            }
        default:
            return false
        }
        return true
    }
}

/// 个人模块路由
struct RouterPersonHandler: GetRouterHandlerDelegate {
    func handler(path: String, arguments: Dict?) -> Bool {
        switch path {
        case GetRouterName.mine_setting.rawValue:
            getllog("跳转个人设置")
            SettingVC.push()
        case GetRouterName.mine_complete.rawValue:
            getllog("跳转完善资料")
            CompleteInfoVC.push()
        default:
            return false
        }
        return true
    }
}

2022-02-17 更新url 编码

/// 路由方法
    static func router(name: String?, arguments: Dict?) {
        guard var name = name else { return }
        
        name = to._urlEncoding(name)
        
        if let urlComponents = URLComponents(string: name) {
            // url 路由,包括协议 native, http, https 比如 native://www.taoqu.com?ios_path=/mine/setting&userId=200944533
            to._parse(urlComponents: urlComponents, argument: arguments)
        } else {
            // 本地路由, 比如 name=/mine/setting
            to.handler?.handler(path: name, arguments: arguments)
        }
    }
private func _urlEncoding(_ url: String) -> String {
//        CharacterSet.urlHostAllowed: 被转义的字符有  "#%/<>?@\^`{|}
//        CharacterSet.urlPathAllowed: 被转义的字符有  "#%;<>?[\]^`{|}
//        CharacterSet.urlUserAllowed: 被转义的字符有   "#%/:<>?@[\]^`
//        CharacterSet.urlQueryAllowed: 被转义的字符有  "#%<>[\]^`{|}
//        CharacterSet.urlPasswordAllowed 被转义的字符有 "#%/:<>?@[\]^`{|}
//        CharacterSet.urlFragmentAllowed 被转义的字符有 "#%<>[\]^`{|}
//        let characterSet = CharacterSet(charactersIn: "#").inverted
        getllog(url)
        let characterSet = CharacterSet.urlQueryAllowed
        let encodingUrl = url.addingPercentEncoding(withAllowedCharacters: characterSet) ?? url
        getllog(encodingUrl)
        return encodingUrl
    }

主要是解决参数中有可能存在 vue 开发模式#home 这种路由路径参数,导致 URLComponents 编码失败,也方便 wkwebview 加载,顺便对中文转个码等等
例:native://www.taoqu.com?ios_path=http&url=www.baidu.com#home?user_id=222222&postId=33333&key=帅奇
转码参数可以自己定制,我这里给出了系统提供的,和自定义的,感兴趣的自己搜搜.

上一篇 下一篇

猜你喜欢

热点阅读