Swift 版的 JDRouter

Swift 版的 JDRouter 完整实现及子模块调用流程,其中scanAndRegisterAllModules自动注册各个模块及其回调的逻辑,其实依然调用了objc相关方法。

1. JDRouter 核心实现

import Foundation
import UIKit

public typealias JDRouterHandler = ([String: Any], ((Any?) -> Void)?) -> Void
public typealias JDInterceptorBlock = (String, [String: Any]?) -> Bool

public final class JDRouter {
    
    // MARK: - Singleton
    public static let shared = JDRouter()
    
    // MARK: - Properties
    private var routeMap: [String: JDRouterHandler] = [:]
    private var interceptors: [JDInterceptorBlock] = []
    private let lock = DispatchQueue(label: "com.jd.router.lock")
    private var autoRegisterEnabled = false
    public var globalCompletion: ((String, TimeInterval) -> Void)?
    
    // MARK: - Initialization
    private init() {}
    
    // MARK: - Route Registration
    public func register(_ url: String, handler: @escaping JDRouterHandler) {
        guard !url.isEmpty else { return }
        lock.sync {
            let normalizedURL = normalize(url)
            routeMap[normalizedURL] = handler
        }
    }
    
    // MARK: - Open URL
    @discardableResult
    public func open(_ url: String) -> Bool {
        return open(url, params: nil, completion: nil)
    }
    
    @discardableResult
    public func open(_ url: String, params: [String: Any]?) -> Bool {
        return open(url, params: params, completion: nil)
    }
    
    @discardableResult
    public func open(_ url: String, params: [String: Any]?, completion: ((Any?) -> Void)?) -> Bool {
        let normalizedURL = normalize(url)
        let startTime = CFAbsoluteTimeGetCurrent()
        
        // Interceptor chain
        for interceptor in interceptors {
            if !interceptor(normalizedURL, params) {
                print("[JDRouter] Intercepted: \(url)")
                return false
            }
        }
        
        // Find handler
        var handler: JDRouterHandler?
        lock.sync {
            handler = routeMap[normalizedURL]
        }
        
        guard let routeHandler = handler else {
            degradeToWebView(normalizedURL, params: params)
            return false
        }
        
        // Merge parameters
        let queryParams = parseQueryParameters(url: normalizedURL)
        var finalParams = params ?? [:]
        queryParams.forEach { key, value in
            // Only add if not already in params to avoid overriding
            if finalParams[key] == nil {
                finalParams[key] = value
            }
        }
        
        // Execute handler
        routeHandler(finalParams) { result in
            completion?(result)
            
            // Performance monitoring
            let endTime = CFAbsoluteTimeGetCurrent()
            self.globalCompletion?(normalizedURL, endTime - startTime)
        }
        
        return true
    }
    
    // MARK: - Interceptor Management
    public func addInterceptor(_ interceptor: @escaping JDInterceptorBlock) {
        lock.sync {
            interceptors.append(interceptor)
        }
    }
    
    public func removeInterceptor(_ interceptor: @escaping JDInterceptorBlock) {
        lock.sync {
            if let index = interceptors.firstIndex(where: { $0 as AnyObject === interceptor as AnyObject }) {
                interceptors.remove(at: index)
            }
        }
    }
    
    // MARK: - Auto Registration
    public static func enableAutoRegister() {
        shared.autoRegisterEnabled = true
        performAutoRegistration()
    }
    
    private static func performAutoRegistration() {
        // Only perform once
        struct Token { static var onceToken = false }
        guard !Token.onceToken else { return }
        Token.onceToken = true
        
        scanAndRegisterAllModules()
    }
    
    private static func scanAndRegisterAllModules() {
        let expectedClassCount = objc_getClassList(nil, 0)
        let allClasses = UnsafeMutablePointer<AnyClass?>.allocate(capacity: Int(expectedClassCount))
        let autoreleasingAllClasses = AutoreleasingUnsafeMutablePointer<AnyClass?>(allClasses)
        let actualClassCount = objc_getClassList(autoreleasingAllClasses, expectedClassCount)
        
        for i in 0..<actualClassCount {
            guard let cls = allClasses[Int(i)] else { continue }
            let className = String(describing: cls)
            // Skip system classes
            if className.hasPrefix("NS") || className.hasPrefix("UI") || className.hasPrefix("_") {
                continue
            }
            
            // Check for class methods
            var methodCount: UInt32 = 0
            guard let methodList = class_copyMethodList(object_getClass(cls), &methodCount) else { continue }
            
            for j in 0..<Int(methodCount) {
                let selector = method_getName(methodList[j])
                let methodName = NSStringFromSelector(selector)
                
                if methodName.hasPrefix("routerHandle_") {
                    let components = methodName.components(separatedBy: "_")
                    if components.count >= 3 {
                        let module = components[1]
                        let interface = components[2]
                        let url = "jd://\(module)/\(interface)"
                        
                        registerDynamicRoute(for: cls, selector: selector, url: url)
                    }
                }
            }
            free(methodList)
        }
        allClasses.deallocate()
    }
    
    private static func registerDynamicRoute(for cls: AnyClass, selector: Selector, url: String) {
        shared.register(url) { params, completion in
            guard cls.responds(to: selector) else {
                print("[JDRouter] Class \(cls) doesn't respond to \(selector)")
                return
            }
            
            let methodSignature = cls.methodSignature(for: selector)
            // 方法签名应该有两个参数:第一个是self,第二个是_cmd,然后是我们传递的两个参数(params和callback)
            if methodSignature?.numberOfArguments != 4 {
                print("[JDRouter] Method argument count mismatch: \(selector)")
                return
            }
            
            // 使用NSInvocation调用类方法
            let invocation = NSInvocation(methodSignature: methodSignature!)
            invocation.target = cls
            invocation.selector = selector
            
            // 设置参数: 第一个参数是self (target已经设置),第二个是_cmd (selector已经设置),然后是我们的两个参数
            // 注意:参数索引从2开始(0和1已经被self和_cmd占用)
            withUnsafePointer(to: params) { paramsPtr in
                invocation.setArgument(paramsPtr, at: 2)
            }
            
            // 注意:由于completion是可选闭包,我们将其转换为Any?以便传递
            let completionWrapper = completion as Any?
            withUnsafePointer(to: completionWrapper) { completionPtr in
                invocation.setArgument(completionPtr, at: 3)
            }
            
            invocation.invoke()
            
            // 如果有返回值,则获取返回值
            if methodSignature?.methodReturnLength ?? 0 > 0 {
                var returnValue: Any?
                invocation.getReturnValue(&returnValue)
                // 如果有completion,则通过completion返回
                completion?(returnValue)
            }
        }
    }
    
    // MARK: - Helper Methods
    private func normalize(_ url: String) -> String {
        return url.trimmingCharacters(in: .whitespaces).lowercased()
    }
    
    private func parseQueryParameters(url: String) -> [String: String] {
        guard let urlComponents = URLComponents(string: url) else { return [:] }
        guard let queryItems = urlComponents.queryItems else { return [:] }
        
        var params: [String: String] = [:]
        for item in queryItems {
            if let value = item.value {
                params[item.name] = value
            }
        }
        return params
    }
    
    private func degradeToWebView(_ url: String, params: [String: Any]?) {
        print("[JDRouter] Degrading to WebView: \(url)")
        // 实际项目中打开H5页面
    }
    
    // MARK: - Debug Tools
    public func printAllRoutes() {
        lock.sync {
            print("====== Registered Routes ======")
            routeMap.keys.forEach { url in
                print("URL: \(url)")
            }
            print("==============================")
        }
    }
    
    public func canOpen(_ url: String) -> Bool {
        let normalizedURL = normalize(url)
        return lock.sync {
            routeMap[normalizedURL] != nil
        }
    }
}

2. 子模块实现(商品模块)

import UIKit

// 模块接口声明宏
public func JDROUTER_EXTERN_METHOD(_ module: String, _ interface: String, _ params: [String: Any], _ callback: ((Any?) -> Void)?) {
    let url = "jd://\(module)/\(interface)"
    JDRouter.shared.open(url, params: params, completion: callback)
}

@objc public class JDProductModule: NSObject {
    
    @objc public class func load() {
        // 确保自动注册已启用
        JDRouter.enableAutoRegister()
    }
    
    // 商品详情页实现
    @objc public class func routerHandle_Product_detail(_ arg: [String: Any], callback: ((Any?) -> Void)?) -> Any? {
        // 1. 参数解析
        guard let productId = arg["id"] as? String else {
            callback?(["status": "error", "message": "Missing product ID"])
            return nil
        }
        
        // 2. 创建视图控制器
        let vc = JDProductDetailViewController()
        vc.productId = productId
        vc.sourceFrom = arg["source"] as? String ?? "unknown"
        
        // 3. 页面跳转
        if let topVC = topViewController() {
            if let nav = topVC.navigationController {
                nav.pushViewController(vc, animated: true)
            } else {
                topVC.present(vc, animated: true, completion: nil)
            }
        }
        
        // 4. 返回结果
        let result: [String: Any] = [
            "status": "success",
            "viewController": vc,
            "timestamp": Date().timeIntervalSince1970
        ]
        callback?(result)
        return result
    }
    
    // 获取顶层控制器
    class func topViewController() -> UIViewController? {
        guard let rootVC = UIApplication.shared.keyWindow?.rootViewController else { return nil }
        return findTopViewController(rootVC)
    }
    
    class func findTopViewController(_ vc: UIViewController) -> UIViewController {
        if let nav = vc as? UINavigationController, let topVC = nav.topViewController {
            return findTopViewController(topVC)
        } else if let tab = vc as? UITabBarController, let selectedVC = tab.selectedViewController {
            return findTopViewController(selectedVC)
        } else if let presented = vc.presentedViewController {
            return findTopViewController(presented)
        }
        return vc
    }
}

3. 拦截器实现(登录检查)

// AppDelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    
    // 添加登录拦截器
    JDRouter.shared.addInterceptor { url, params -> Bool in
        // 需要登录的页面路径
        if url.contains("jd://product/detail") || url.contains("jd://order/create") {
            if !UserManager.shared.isLogin {
                print("[JDRouter] Login required, redirecting to login")
                
                // 保存原始请求
                let redirectInfo: [String: Any] = [
                    "url": url,
                    "params": params ?? [:]
                ]
                UserDefaults.standard.set(redirectInfo, forKey: "redirectAfterLogin")
                
                // 跳转到登录页
                JDRouter.shared.open("jd://user/login")
                return false // 中断原始跳转
            }
        }
        return true
    }
    
    // 启用自动注册
    JDRouter.enableAutoRegister()
    
    return true
}

4. 调用商品模块的完整流程

func didSelectProduct(_ productId: String) {
    let url = "jd://product/detail?id=\(productId)&source=home"
    
    JDRouter.shared.open(url, params: ["promotion": "summer_sale", "position": selectedIndex]) { result in
        if let resultDict = result as? [String: Any],
           resultDict["status"] as? String == "success" {
            print("✅ Successfully opened product page: \(productId)")
        } else {
            print("❌ Failed to open product page")
        }
    }
}

5. 完整调用时序图

完整调用时序图

6. 关键步骤解析

  1. 自动注册流程
    自动注册流程
  1. 调用过程
    调用过程
  1. 参数合并逻辑

    URL参数: id=123&source=home
    代码参数: ["promotion": "summer_sale"]
    合并结果: [
        "id": "123",
        "source": "home",
        "promotion": "summer_sale"
    ]
    
  2. 跨模块通信

    // 支付结果回调示例
    JDRouter.shared.open("jd://order/pay", params: ["order_id": "O2023001"]) { result in
        if let resultDict = result as? [String: Any] {
            if resultDict["status"] as? String == "success" {
                self.showSuccess("支付成功")
            } else {
                self.showError(resultDict["reason"] as? String)
            }
        }
    }
    

7. 高级功能扩展

  1. 动态路由更新
extension JDRouter {
    public func updateRoutesFromServer(_ config: [String: Any]) {
        guard let routes = config["routes"] as? [String] else { return }
        
        lock.sync {
            // 清空现有路由
            routeMap.removeAll()
            
            // 注册新路由
            routes.forEach { url in
                let normalizedURL = normalize(url)
                routeMap[normalizedURL] = { [weak self] params, completion in
                    self?.openWebURL(url, params: params)
                }
            }
        }
    }
    
    private func openWebURL(_ url: String, params: [String: Any]?) {
        print("Opening web URL: \(url)")
        // 实际项目中打开H5页面
    }
}
  1. AOP 路由监控
// 在 AppDelegate 中配置
JDRouter.shared.globalCompletion = { url, duration in
    Analytics.track(event: "router_performance", properties: [
        "url": url,
        "duration": duration,
        "timestamp": Date().timeIntervalSince1970
    ])
    
    if duration > 0.5 {
        Crashlytics.log("Slow router: \(url) (\(duration)s)")
    }
}
  1. 路由调试面板
class RouterDebugViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        JDRouter.shared.printAllRoutes()
    }
    
    func testRoute(_ url: String) {
        if JDRouter.shared.canOpen(url) {
            JDRouter.shared.open(url) { result in
                print("Test result: \(result ?? "nil")")
            }
        } else {
            print("Route not registered: \(url)")
        }
    }
}

8. 实际应用场景

  1. 普通页面跳转
// 打开商品详情页
JDRouter.shared.open("jd://product/detail?id=10086", 
                     params: ["source": "recommend"])
  1. 服务调用
// 加入购物车
JDRouter.shared.open("jd://cart/add", params: [
    "product_id": "P1001",
    "quantity": 2,
    "sku": "red_large"
])
  1. 带回调的操作
// 支付操作
JDRouter.shared.open("jd://order/pay", params: ["order_id": "O2023001"]) { result in
    if let resultDict = result as? [String: Any] {
        if resultDict["status"] as? String == "success" {
            self.showSuccess("支付成功")
        } else {
            self.showError(resultDict["reason"] as? String)
        }
    }
}

总结

Swift 版 JDRouter 实现了以下核心功能:

  1. 路由注册机制

    • 自动扫描 routerHandle_ 前缀的类方法
    • 动态注册路由表
    • 支持手动注册
  2. 路由调用流程

    • URL 标准化处理
    • 拦截器链式执行
    • 参数自动合并
    • Completion 回调支持
  3. 商品模块调用流程

    • 自动注册商品详情路由
    • 登录拦截器检查
    • 参数解析与页面跳转
    • 结果返回
  4. 高级特性

    • 动态路由更新
    • AOP 性能监控
    • 路由调试工具
    • 安全降级机制
image.png

优势:

  • 自动化注册:减少手动注册代码
  • 安全调用:拦截器统一管理权限
  • 灵活扩展:支持动态路由更新
  • 性能监控:内置 AOP 监控系统
  • 类型安全:Swift 强类型保障

此实现保持了 Objective-C 版本的核心功能,同时利用 Swift 的现代语法特性(泛型、闭包、协议等)进行了优化,更适合 Swift 项目使用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • JDRouter 完整实现及子模块调用流程 1. JDRouter 核心实现 JDRouter.h JDRoute...
    大成小栈阅读 27评论 0 0
  • LARouter-Swift一个用于模块间解耦和通信,基于Swift协议进行动态懒加载注册路由与打开路由的工具。同...
    AKyS佐毅阅读 1,118评论 0 1
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 135,273评论 19 139
  • 以下文章转载自知乎,暗灭-京华九月秋近寒,浮沉半生影长单. 暗灭 京华九月秋近寒,浮沉半生影长单 10,850 人...
    ve追风_685b阅读 4,146评论 1 15
  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架,建立于...
    Hsinwong阅读 22,660评论 1 92