背景

大多 Swift 的路由都是基于原有 OC 的 Bus 和 URL 的办法承继过来的,可是跟着主力言语切换到 Swift,以及在 Swift 中运用的越来越多,也越发的感觉到不【适应】。

类型转化问题

Swift 是一门强类型言语,更倾向于运用确认的类型参数,尽量防止运用 Any or AnyObject。相比之下,在 OC 言语中,从字典中获取字段后进行类型转化相对便利,只需运用 boolValue、intValue、floatValue 等办法即可完成(在不考虑类型安全的情况下)。然而,在 Swift 中,对于 Int、Bool、Float 等类型的转化,咱们需求编写更多的额外代码来处理。
比方下面咱们需求从字典中获取某个字段:

// 是否需求躲藏导航栏
var hideNavi = false
if let value = params?[WebHideNaviKey] as? String {
    // 传字符
    hideNavi = value == "1"
} else if let value = params?[WebHideNaviKey] as? Bool {
    // 传 Bool
    hideNavi = value
} else if let value = params?[WebHideNaviKey] as? Int {
    // 传 Int
    hideNavi = value == 1
}
// 是否需求躲藏导航栏
BOOL hideNavi = [params[WebHideNaviKey] boolValue];

注册问题

咱们都知道,运用 URL 的办法都需求在冷启的时候进行注册,通过注册 [String: String]以及 String -> Class 的映射,生成一个比较大的字典busObjectMap: [String: NSObject],终究被单例永久持有,用于后续的事情分发。

一方面生成实例对象是一个耗时操作,影响冷启耗时,跟着版别迭代,模块越多对冷启的影响会越大。另一方面在 Swift 中,由于 Modules 的概念,注册的时候,value 值有必要是 AppTarget.ClassName,假如后续做了组件化,value 值有必要是 AppTarget.PodName.ClassName,这样的办法对后续的保护和扩展都有很大的影响。

为什么抛弃 URL

先看下为什么一开始挑选 URL 的办法做路由,很大一部分原因是 web 跳转原生页面以及三方跳转 App 都是通过 URL 的办法,无论终究挑选任何路由,都防止不了对 URL 的解析处理。既然防止不了,那就一致,这样既能够处理 web 跳原生、三方跳转 App,还能够解决 App 内跳转导致的模块耦合问题。

那为什么抛弃了 URL呢?上面提到了 URL 解决了两方面的问题,一、web和三方跳转原生页面,二、原生 App 之间的跳转,但实际上他们并不彻底一致,甚至会有互相冲突的当地。
比方:从图片列表跳转图片详情页,当前在列表页现已获取到图片数据 item

/* 这儿假定图片无需存储本地 */
// 只考虑 App 内跳转,直接传图片
let detailVC = ImageDetailViewController(image: item.image)
// 考虑三方跳转的话,传图片地址
Bus.call("image/detail", params:[url: item.imageURLStr])

假如两种计划都存在,会导致保护本钱成倍提高,而假如一致运用第二种计划的话,对 App 内跳转就会有以下问题:

  1. 现已拿到 Image 的前提下,由于第二种 case 的存在,只能传图片地址,图片需求二次下载,页面会有一个 loading 状况,用户体验会下降。
  2. 假如咱们传图片的话,params 的类型只能是 [String: Any],接纳端需求写很多的胶水代码来处理各种类型转化,这也太不 Swift 了。

此外,硬编码传参问题一直备受诟病,跟着版别迭代,保护难度呈指数级增加。终究,咱们决定抛弃 URL 计划。

实践计划

基于以上背景,咱们一直在思考一种能兼容多种情况的路由计划。
并没有,因为无论哪种办法,都绕不过对 URL 的处理,那么,就防止不了 URL 中各种字符串的解析。

可是咱们想清楚了别的一件事,web 和三方跳原生仅仅原生内部跳转的一个子集,仅仅其中的一些特例,咱们不应该因为特例而影响了对整个工程的规划,导致咱们在运用过程中束手束脚。咱们更多的心思还是要花在整个工程的架构规划宽和耦上,因此产生了 Protocol Router 和 LinkHandler 两种处理计划。

Protocol Router

Protocol 的优势不仅在于约好了详细的参数类型,跳转 VC 的时候清晰的知道其所需求的参数传递,一起对外暴露的特点也能够放到 Protocol 里。
同一层级之间只需求在运用的时候,注入所依靠的 Protocol,当然也能够把一切的 Protocol Router 下沉,上层直接依靠。

// 协议声明
public protocol VIPRouter {
	func gotoPurchase(from type: PurchaseFromType)
}
// 详细的 module 来完成协议
class CEVIPModule: VIPRouter {
    func gotoPurchase(from type: PurchaseFromType) {
        let purchaseVC = CEPurchaseViewController(fromType: type)
        openVC(purchaseVC)
    }
}
// 依靠注入
class BaseFoundation {
    static var vipRouter: VIPRouter?
	static func registerVIPRouter(_ router: VIPRouter) {
    	self.vipRouter = router
    }
}
// 协议调用
class ModuleViewController {
	func buttonAction() {
    	BaseFoundation.vipRouter?.gotoPurchase(from: .cloud)
    }
}

LinkHandler

用于特别处理 URL,只用于处理 web/三方跳转 App,针对详细的 URL 做好分类,然后分发到详细的 LinkModuleHandler,终究调用详细的 Protocol Router 来完成跳转。既防止了注册大字典,也不影响事务完成。

class LinkHandler {
    /// 假定 url 为 https://com.example/module/path?param=xxxx
	static func handleURL(_ url: URL) {
        // url 拆分红 module、path、参数字典
        let result = splitURL(url)
        let module = result.module
        let path = result.path
        let params = result.params
        if module == Module.vip { // 这儿运用枚举会更好,为了削减代码,直接 if 判断了
        	LinkVIPHandler.handlePath(path, params: params)
        }
    }
}
class LinkVIPHandler {
	static func handlePath(_ path: String, params: [String: String]?) {
        if path == VIPPath.purchase { // 这儿运用枚举会更好,为了削减代码,直接 if 判断了
            var fromType: PurchaseFromType = .none
            if let from = params["from"],
               let type = PurchaseFromType(rawValue: from) {
                fromType = type
            }
            BaseFoundation.vipRouter?.gotoPurchase(from: fromType)
        }
    }
}

小结

目前项目中已彻底替换了曾经的 Bus 计划,灰度全量了的 Router + Link 计划。在后续两个版别的运用过程中十分舒服,效率也有很大提高,跳转所需参数只需求检查协议即可清晰明了。再不用像曾经一样,跳转需求先检查 Bus,再检查参数所需各种字符串依靠,再张贴复制来防止字符串犯错。

当然实践过程中咱们也约好了一个规范,一切 VC 初始化依靠的参数,必须放初始化办法里面,尽可能少的通过特点来赋值,这一点很要害也很重要。

一家之言,欢迎拍砖。