Swift路由组件(二)路由的实现
方案的选择
具体一个路由,他真正要做什么事情呢,总结一下应该是这几个逻辑功能:
通过key来map到一个controller
实例化一个controller
解决好传参
页面跳转
下面讲讲具体代码如何实现每一个逻辑。
一、通过key来map到一个controller
方法有两种:
通过维护一个plist文件,然后开发的时候添加好路由表。
在运行的时候通过业务注册,每个业务把key注册到路由里面去,在内存中维护一个路由表。
先说1,这个的做法也简单。定义的plist如下:
这样运行的时候就可以通过把这个plist读进来,然后就可以做map了。
再说2.这个的简单做法是做一个入口函数,然后各个页面自己把注册的代码添加到这个入口函数里面。比如:
// 启动先初始化好 func onLaunch() { XXRouter.register(to: "native://to_home_page", routerClass: HomeViewController.self) XXRouter.register(to: "native://to_buy_page", routerClass: BuyViewController.self) XXRouter.register(to: "http", routerClass: WebViewController.self) XXRouter.register(to: "https", routerClass: WebViewController.self) } 复制代码
两种方式都需要有一个入口的地方,类似于集中存放。集中存放也是有好处的,方便维护,直观,一眼知道有多少定义,不好的地方就是不方便做组件化。而且组件化还是大家很迫切需要的。
那么这里的需求
就是能不能分散定义。
首先我是使用方案2,然后这里我又想到了一种巧妙的方法,先定义一个总的XXRouter类,然后让各个业务自己去extension XXRouter的方法。然后在运行的时候,就可以通过遍历一个类有多少方法,然后再动态的去调用这些方法得到返回值,这样就知道有多少路由定义了。
如下,先定义XXRouter的扩展。
// 添加路由表 extension XXRouter { @objc func router_WebController() -> XXRouterModel { return XXRouterModel(to: "http", routerClass: WebController.self) } @objc func router_WebController_https() -> XXRouterModel { return XXRouterModel(to: "https", routerClass: WebController.self) } } 复制代码
这个多定义的方法的要求如下:
必须定义为@objc,依赖运行时。
必须router_开头的函数名,后面可以随便定义名字,建议用路由的类名。如:路由类WebController, 就可以定义函数router_WebController
必须函数的入参为空。
这个函数返回值要求是一个XXRouterModel,他的值有: 1)to: 路由名的定义,如:native://course,或者http等唯一的key, 做好分层命名。 2)routerClass: 路由目标类,如:TestClassName.self
运行时遍历和动态调用如下:
private static func configRoutersFromMethodList() -> [String: XXRouterModel] { var routerList: [String: XXRouterModel] = [:] var methodCount: UInt32 = 0 let methodList = class_copyMethodList(XXRouter.self, &methodCount) if let methodList = methodList, methodCount > 0 { for i in 0..<Int(methodCount) { let selName = sel_getName(method_getName(methodList[i])) if let methodName = String(cString: selName, encoding: .utf8), methodName.hasPrefix("router_") { let selector: Selector = NSSelectorFromString(methodName) if XXRouter.shared.responds(to: selector) { if let result = XXRouter.shared.perform(selector).takeUnretainedValue() as? XXRouterModel { routerList[result.to] = result } } } } } free(methodList) return routerList } 复制代码
遍历一个类的所有方法
拿出以router_开头的那些方法。
动态调用router_开头的方法,获取他的返回值,保存为路由表。
二、实例化一个controller
从路由层面来看也是有两种方法,要么路由自动实例化。要么让实现路由的页面自己实例化。
如,路由里面自动做实例化, 那就是接收一个AnyClass,然后利用NSObject的init(), 方法自动实例化:
private static func getInstance(_ cls: AnyClass) -> XXRoutable? { if let instance = (cls as? NSObject.Type)?.init(), let routable = (instance as? XXRoutable) { return routable } return nil } 复制代码
如,让各个页面自己实例话好,再传给路由层。
// 实现路由协议,让自己支持路由跳转。 extension WebController: XXRoutable { // 返回一个路由协议的实例 static func createInstance(params: [String : Any]) -> XXRoutable { let vc = WebController() vc.url = params["to"] as? String ?? "" // 路由传参数 vc.value = params["value"] as? String ?? "" // 路由传参数 return vc } } 复制代码
这里两种方式都可以,我选择了后面这种,让业务自己实例化好再给路由层,感觉合理些。包括他还可以处理好参数再给回来。
解决好传参
通过上面的方案就能把参数都搞定了。
页面跳转
这个就更简单了。无非就是拿到navigationController,然后push。为了方便,我这里写了一个全局的获取navigationController来做push。看代码
/// 当前的导航控制器 public static func currentNavigationController() -> UINavigationController? { return currentController().navigationController } public static func currentController() -> UIViewController { if let root = delegate().window??.rootViewController { return getCurrent(controller: root) } else { print("异常问题, 还没有rootVC不应该调用") assert(false, "异常问题, 还没有rootVC不应该调用") return UIViewController() } } /// 通过递归拿到当前显示的UIViewController public static func getCurrent(controller: UIViewController) -> UIViewController { if controller is UINavigationController { let naviController = controller as! UINavigationController return getCurrent(controller: naviController.viewControllers.last!) } else if controller is UITabBarController { let tabbarController = controller as! UITabBarController return getCurrent(controller: tabbarController.selectedViewController!) } else if controller.presentedViewController != nil { return getCurrent(controller: controller.presentedViewController!) } else { return controller } } 复制代码
有以上的代码,可以这样获取一个全局的navigationController
RouterUtil.currentNavigationController() 复制代码
那么push就简单了。
RouterUtil.currentNavigationController()?.pushViewController(controller, animated: animated) 复制代码
具体实现
定义路由协议
通过定义一个路由协议,让实现路由协议的页面都能有跳转的能力。定义一个XXRoutable的协议如下:
/// 路由协议, 需要是AnyObject,其他如struct和enum等不能实现这个协议 public protocol XXRoutable: AnyObject { /// 路由传参,接收者负责解析自己的参数并返回一个路由实例 static func createInstance(params: [String: Any]) -> XXRoutable /// 路由逻辑处理 func executeRouter(params: [String: Any], navRootVC: UIViewController?) } 复制代码
让实现路由的页面,要返回一个实例,实现createInstance方法返回。
路由要执行的具体跳转逻辑,通过实现executeRouter方法实现。也可以不实现这个,路由组件做了默认的路由跳转。
默认跳转如下:
/// 路由协议的默认实现 public extension XXRoutable { /// 默认路由跳转 func executeRouter(params: [String: Any] = [:], navRootVC: UIViewController? = nil) { guard let controller = self as? UIViewController else { assert(false, "默认路由跳转,需要routable继承UIViewController") return } defaultPush(to: controller, params: params, navRootVC: navRootVC) } } /// 路由里面的私有方法 public extension XXRoutable { /// native://my?userId=1&token=jdfsakbfjkafbf /// /// - Parameters: /// - controller: 跳转VC /// - params: 额外参数 /// - navRootVC 有的时候不需要取currentVC func defaultPush(to controller: UIViewController, params: [String: Any] = [:], navRootVC: UIViewController? = nil) { let animated = (params["animated"] as? Bool) ?? true if navRootVC?.navigationController != nil { navRootVC?.navigationController?.pushViewController(controller, animated: animated) } else { RouterUtil.currentNavigationController()?.pushViewController(controller, animated: animated) } } } 复制代码
注册路由表
前面说过添加路由表通过extension XXRouter方法来实现,然后扩展的方法里面统一返回一个路由表模型。模型定义如下
public class XXRouterModel: NSObject { /// 路由名,可以任意定义一个唯一的key /// 如:native://course/detail,这样的一个规则表示本地的某个页面。 /// 或者http,或者https,这两个表示是url网页,直接用webview去响应。 public var to: String = "" public var routerClass: AnyClass = XXRouterModel.self // 路由目标类 /// 路由模块的路由表定义 /// - Parameters: /// - to: 路由名的定义 /// - routerClass: 路由目标类 public convenience init(to: String, routerClass: AnyClass) { self.init() self.to = to self.routerClass = routerClass } } 复制代码
校验
路由的定义
通过上面,让一个页面实现路由,那么只需要实现一下路由协议,按路由协议实现对应的方法就能够有路由的功能。再加上一个extension方法去添加一个路由表就行。 整体定义少的话就两个方法,如下:
// 正常的路由定义 extension TestClassName: Routable { static func createInstance(params: [String : Any]) -> Routable { return TestClassName() } } // 多定义一个extension方法 // 添加路由表 extension Router { @objc func router_TestClassName() -> RouterModel { return RouterModel(to: "native://testTest", routerClass: TestClassName.self) } } 复制代码
路由的使用
XXRouter.pushTo(jumpParams: ["to": "native://testTest", "name": "1"]) 复制代码
自动校验
看上面的定义,非常简单,就两个方法。 不过这两个方法的定义都是有要求的。那么假如使用者没有按规则定义怎么办。
比如,类已经实现了Routable协议,但是忘记加extension的router_xxx方法添加路由表的,会路由失败。
比如,添加了router_xxx方法添加了路由表,但是忘记实现Routable协议的。
比如,添加了router_xxx方法添加了路由表,但是添加的router_xxx方法,没有按规定,导致方法不生效。(@objc,函数命名,函数入参,函数返回值等不规范)
比如,添加的路由表的key重复了,怎么办。
为了解决这些遗漏的问题,我写了一个自动校验的函数。通过路由初始化的时候,把上面的问题全部检查一遍,有问题直接中断,然后控制住这函数只在Debug里面才运行就行,不影响线上。函数如下:
#if DEBUG /// 自动检查所有的路由设置是否符合规范,当发现不符合规范的路由设置,直接中断 private static func checkRoutableClassesSettingIsConform() { guard !isCheck else { return } // 只检查一次 let expectedClassCount = objc_getClassList(nil, 0) let allClasses = UnsafeMutablePointer<AnyClass>.allocate(capacity: Int(expectedClassCount)) let autoreleasingAllClasses = AutoreleasingUnsafeMutablePointer<AnyClass>(allClasses) let actualClassCount: Int32 = objc_getClassList(autoreleasingAllClasses, expectedClassCount) for i in 0 ..< actualClassCount { let currentClass: AnyClass = allClasses[Int(i)] if (class_getInstanceMethod(currentClass, NSSelectorFromString("methodSignatureForSelector:")) != nil), (class_getInstanceMethod(currentClass, NSSelectorFromString("doesNotRecognizeSelector:")) != nil), let cls = currentClass as? XXRoutable.Type { var isSet = checkList["\(cls)"] if isSet == nil { var curCls: AnyClass = cls as AnyClass // 只要有一个父亲添加了路由表,就表示ok,因为路由那边是不允许子类实现路由协议的,子类只能继承,只能override,或者换别的类去实现路由 while let superCls = curCls.superclass() { if checkList["\(superCls)"] != nil { isSet = true break } curCls = superCls } } assert(isSet != nil, "\(cls)有实现Routable协议,但是没有添加路由表,或者路由表配置没有按规范,请检查:\(cls)。") checkList["\(cls)"] = true } } for (key, value) in checkList where value == false { assert(false, "\(key)有添加路由表,但是没有实现Routable协议,请检查:\(key)。") } isCheck = true } #endif 复制代码
函数做两个事情
有添加路由表,但是没有实现Routable协议
有实现Routable协议,但是没有添加路由表,或者路由表配置没有按规范定义。
还缺少一个路由表重复。这个可以在添加路由表的时候加多一个判断就能解决,
作者:清点游玩
链接:https://juejin.cn/post/7032214542528544805