路由机制概述
为什么需要路由?
┌─────────────────────────────────────────────────────────────────┐
│ 传统页面跳转 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ 直接依赖 ┌──────────────┐ │
│ │ 首页模块 │ ─────────────────────→ │ 商品模块 │ │
│ │ │ import ProductModule │ │ │
│ │ HomeVC │ ───────────────────────→│ ProductVC │ │
│ └──────────────┘ └──────────────┘ │
│ │ │ │
│ │ 直接依赖 │ │
│ └─────────────────────┐ ┌─────────────┘ │
│ ▼ ▼ │
│ ┌──────────────┐ │
│ │ 订单模块 │ │
│ │ OrderVC │ │
│ └──────────────┘ │
│ │
│ 问题: │
│ ❌ 模块间强耦合,形成网状依赖 │
│ ❌ 无法独立编译和测试 │
│ ❌ 代码改动影响范围大 │
│ ❌ 不支持动态化和外部跳转 │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 路由解耦后 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 首页模块 │ │ 商品模块 │ │
│ └──────┬───────┘ └───────▲──────┘ │
│ │ │ │
│ │ router.navigate("/product/123") │ │
│ │ │ │
│ ▼ │ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 路由中心 │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ 路由表: │ │ │
│ │ │ /home → HomeViewController │ │ │
│ │ │ /product/:id → ProductViewController │ │ │
│ │ │ /order/:id → OrderViewController │ │ │
│ │ │ /cart → CartViewController │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ 订单模块 │ │
│ └──────────────┘ │
│ │
│ 优势: │
│ ✅ 模块间完全解耦 │
│ ✅ 支持外部 DeepLink │
│ ✅ 动态化配置 │
│ ✅ 统一拦截和处理 │
└─────────────────────────────────────────────────────────────────┘
主流路由方案对比
┌──────────────────────────────────────────────────────────────────────────┐
│ iOS 主流路由方案对比 │
├────────────────┬────────────────┬────────────────┬───────────────────────┤
│ 方案 │ 代表框架 │ 优点 │ 缺点 │
├────────────────┼────────────────┼────────────────┼───────────────────────┤
│ URL Router │ JLRoutes │ • 解耦彻底 │ • 参数传递受限 │
│ │ MGJRouter │ • 支持DeepLink │ • 编译期无类型检查 │
│ │ HHRouter │ • 配置灵活 │ • 硬编码URL字符串 │
├────────────────┼────────────────┼────────────────┼───────────────────────┤
│ Target-Action │ CTMediator │ • 无需注册 │ • 依赖runtime │
│ │ │ • 延迟加载 │ • 方法名硬编码 │
│ │ │ • 原生调用 │ • 缺少编译检查 │
├────────────────┼────────────────┼────────────────┼───────────────────────┤
│ Protocol-Class │ BeeHive │ • 类型安全 │ • 需要注册协议 │
│ │ Swinject │ • 编译期检查 │ • 接口变更成本高 │
│ │ │ • IDE支持好 │ • 协议维护成本 │
├────────────────┼────────────────┼────────────────┼───────────────────────┤
│ 混合方案 │ 自研Router │ • 各取所长 │ • 架构复杂度较高 │
│ │ │ • 适应不同场景 │ • 学习成本 │
└────────────────┴────────────────┴────────────────┴───────────────────────┘
URL Scheme 机制
URL Scheme 基础
/*
═══════════════════════════════════════════════════════════════════
URL Scheme 结构
═══════════════════════════════════════════════════════════════════
myapp://product/detail?id=123&source=home
───── ───────────── ─────────────────────
│ │ │
Scheme Path Query
(协议) (路径) (查询参数)
完整URL结构:
┌────────────────────────────────────────────────────────────────┐
│ scheme://user:password@host:port/path?query=value#fragment │
└────────────────────────────────────────────────────────────────┘
常见使用场景:
┌─────────────────────────────────────────────────────────────────┐
│ 场景 │ 示例URL │
├─────────────────────────────────────────────────────────────────┤
│ App外部打开 │ myapp://home │
│ Push通知跳转 │ myapp://order/123 │
│ 第三方分享回调 │ myapp://share/callback?code=xxx │
│ 广告投放追踪 │ myapp://product/456?utm_source=ad │
│ App间跳转 │ weixin:// │
│ Universal Links │ https://app.example.com/product/1 │
└─────────────────────────────────────────────────────────────────┘
*/
Info.plist 配置
<!-- Info.plist URL Scheme 配置 -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- URL Types 配置 -->
<key>CFBundleURLTypes</key>
<array>
<!-- 主 Scheme -->
<dict>
<key>CFBundleURLName</key>
<string>com.yourcompany.myapp</string>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
<key>CFBundleTypeRole</key>
<string>Editor</string>
</dict>
<!-- 微信回调 Scheme -->
<dict>
<key>CFBundleURLName</key>
<string>weixin</string>
<key>CFBundleURLSchemes</key>
<array>
<string>wx1234567890abcdef</string>
</array>
</dict>
<!-- 支付宝回调 Scheme -->
<dict>
<key>CFBundleURLName</key>
<string>alipay</string>
<key>CFBundleURLSchemes</key>
<array>
<string>ap2021123456789012</string>
</array>
</dict>
</array>
<!-- 可以打开的外部 Scheme(iOS 9+需要白名单) -->
<key>LSApplicationQueriesSchemes</key>
<array>
<string>weixin</string>
<string>wechat</string>
<string>alipay</string>
<string>mqq</string>
<string>weibo</string>
</array>
</dict>
</plist>
Universal Links 配置
/*
═══════════════════════════════════════════════════════════════════
Universal Links
═══════════════════════════════════════════════════════════════════
优势对比:
┌────────────────────┬─────────────────┬─────────────────────────┐
│ 特性 │ URL Scheme │ Universal Links │
├────────────────────┼─────────────────┼─────────────────────────┤
│ 唯一性 │ ❌ 可能冲突 │ ✅ 域名唯一 │
│ 回退网页 │ ❌ 不支持 │ ✅ 可打开网页 │
│ 微信等App内打开 │ ❌ 被屏蔽 │ ✅ 可直接打开 │
│ 安全性 │ ❌ 任何App可用 │ ✅ 域名验证 │
│ 配置复杂度 │ 简单 │ 需服务端配合 │
└────────────────────┴─────────────────┴─────────────────────────┘
*/
// MARK: - apple-app-site-association 文件 (放在服务器 .well-known 目录)
/*
{
"applinks": {
"apps": [],
"details": [
{
"appID": "TEAMID.com.yourcompany.myapp",
"paths": [
"/product/*",
"/order/*",
"/user/*",
"/share/*",
"NOT /api/*",
"NOT /static/*"
]
}
]
},
"webcredentials": {
"apps": ["TEAMID.com.yourcompany.myapp"]
}
}
*/
// MARK: - Entitlements 配置
/*
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:app.yourcompany.com</string>
<string>applinks:www.yourcompany.com</string>
</array>
</dict>
</plist>
*/
URL 处理入口
// MARK: - SceneDelegate (iOS 13+)
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
// 冷启动时通过 URL 打开
func scene(_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
// 处理 URL Scheme
if let url = connectionOptions.urlContexts.first?.url {
handleIncomingURL(url, isLaunch: true)
}
// 处理 Universal Links
if let userActivity = connectionOptions.userActivities.first,
userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL {
handleUniversalLink(url, isLaunch: true)
}
}
// App 在后台时通过 URL 唤起
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
guard let url = URLContexts.first?.url else { return }
handleIncomingURL(url, isLaunch: false)
}
// Universal Links
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL else {
return
}
handleUniversalLink(url, isLaunch: false)
}
// MARK: - URL Handling
private func handleIncomingURL(_ url: URL, isLaunch: Bool) {
print("📥 [URL] Incoming: (url.absoluteString), isLaunch: (isLaunch)")
// 检查是否是第三方回调
if handleThirdPartyCallback(url) {
return
}
// 使用路由系统处理
DispatchQueue.main.asyncAfter(deadline: .now() + (isLaunch ? 0.5 : 0)) {
URLRouter.shared.open(url: url, from: self.topViewController())
}
}
private func handleUniversalLink(_ url: URL, isLaunch: Bool) {
print("🔗 [UniversalLink] Incoming: (url.absoluteString)")
// 转换为内部 URL 格式
let internalURL = convertToInternalURL(url)
handleIncomingURL(internalURL, isLaunch: isLaunch)
}
private func handleThirdPartyCallback(_ url: URL) -> Bool {
let scheme = url.scheme ?? ""
// 微信回调
if scheme.hasPrefix("wx") {
return WXApi.handleOpen(url, delegate: WXApiManager.shared)
}
// 支付宝回调
if scheme.hasPrefix("ap") {
AlipaySDK.defaultService().processOrder(withPaymentResult: url) { result in
// 处理支付结果
}
return true
}
return false
}
private func convertToInternalURL(_ url: URL) -> URL {
// https://app.yourcompany.com/product/123 -> myapp://product/123
var components = URLComponents(url: url, resolvingAgainstBaseURL: false)
components?.scheme = "myapp"
components?.host = nil
return components?.url ?? url
}
private func topViewController() -> UIViewController? {
guard let rootVC = window?.rootViewController else { return nil }
return findTopViewController(from: rootVC)
}
private func findTopViewController(from viewController: UIViewController) -> UIViewController {
if let nav = viewController as? UINavigationController,
let topVC = nav.topViewController {
return findTopViewController(from: topVC)
}
if let tab = viewController as? UITabBarController,
let selectedVC = tab.selectedViewController {
return findTopViewController(from: selectedVC)
}
if let presentedVC = viewController.presentedViewController {
return findTopViewController(from: presentedVC)
}
return viewController
}
}
// MARK: - AppDelegate (iOS 12 及以下兼容)
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
// iOS 12 及以下的 URL 处理
func application(_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
print("📥 [URL] Open: (url.absoluteString)")
// 获取来源App信息
let sourceApp = options[.sourceApplication] as? String
let annotation = options[.annotation]
print(" Source App: (sourceApp ?? "unknown")")
// 处理URL
handleIncomingURL(url)
return true
}
// Universal Links (iOS 12 及以下)
func application(_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL else {
return false
}
handleUniversalLink(url)
return true
}
private func handleIncomingURL(_ url: URL) {
URLRouter.shared.open(url: url, from: nil)
}
private func handleUniversalLink(_ url: URL) {
// 转换并处理
handleIncomingURL(url)
}
}
URLRouter 路由方案
完整 URLRouter 实现
// MARK: - URL路由核心实现
import UIKit
import Combine
// ═══════════════════════════════════════════════════════════════════
// MARK: - 路由协议定义
// ═══════════════════════════════════════════════════════════════════
/// 路由结果
public enum RouteResult {
case viewController(UIViewController)
case handler(() -> Void)
case redirect(URL)
case failed(RouteError)
}
/// 路由错误
public enum RouteError: Error, LocalizedError {
case notFound(path: String)
case invalidParameter(String)
case requireLogin
case accessDenied(reason: String)
case custom(Error)
public var errorDescription: String? {
switch self {
case .notFound(let path): return "路由未找到: (path)"
case .invalidParameter(let msg): return "参数无效: (msg)"
case .requireLogin: return "需要登录"
case .accessDenied(let reason): return "访问被拒绝: (reason)"
case .custom(let error): return error.localizedDescription
}
}
}
/// 导航类型
public enum NavigationType {
case push // Push导航
case present // 模态弹出
case presentFullScreen // 全屏模态
case replace // 替换当前页
case root // 设为根控制器
case custom((UIViewController, UIViewController) -> Void)
}
/// 路由选项
public struct RouteOptions {
public var navigationType: NavigationType
public var animated: Bool
public var completion: (() -> Void)?
public init(navigationType: NavigationType = .push,
animated: Bool = true,
completion: (() -> Void)? = nil) {
self.navigationType = navigationType
self.animated = animated
self.completion = completion
}
public static let `default` = RouteOptions()
public static let present = RouteOptions(navigationType: .present)
public static let presentFullScreen = RouteOptions(navigationType: .presentFullScreen)
}
/// 路由处理器协议
public protocol RouteHandler {
func handle(url: URL, params: RouteParams) -> RouteResult
}
/// 路由拦截器协议
public protocol RouteInterceptor {
/// 拦截器优先级(数字越大越先执行)
var priority: Int { get }
/// 是否拦截该路由
func shouldIntercept(url: URL, params: RouteParams) -> Bool
/// 处理拦截(返回nil表示继续路由,返回URL表示重定向)
func intercept(url: URL, params: RouteParams,
completion: @escaping (InterceptResult) -> Void)
}
/// 拦截结果
public enum InterceptResult {
case `continue` // 继续路由
case redirect(URL) // 重定向
case reject(RouteError) // 拒绝路由
case handled // 已处理,不需要继续
}
extension RouteInterceptor {
public var priority: Int { return 0 }
}
// ═══════════════════════════════════════════════════════════════════
// MARK: - 路由参数
// ═══════════════════════════════════════════════════════════════════
/// 路由参数容器
public struct RouteParams {
/// URL中的路径参数 (如 /product/:id 中的 id)
public var pathParams: [String: String] = [:]
/// URL中的查询参数 (如 ?page=1&size=10)
public var queryParams: [String: String] = [:]
/// 额外传递的对象参数
public var extra: [String: Any] = [:]
/// 来源URL
public var sourceURL: URL?
/// 获取String参数
public func string(_ key: String) -> String? {
return pathParams[key] ?? queryParams[key] ?? extra[key] as? String
}
/// 获取Int参数
public func int(_ key: String) -> Int? {
if let value = string(key) {
return Int(value)
}
return extra[key] as? Int
}
/// 获取Bool参数
public func bool(_ key: String) -> Bool {
if let value = string(key) {
return ["true", "1", "yes"].contains(value.lowercased())
}
return extra[key] as? Bool ?? false
}
/// 获取任意类型参数
public func get<T>(_ key: String) -> T? {
return extra[key] as? T
}
/// 合并参数
public mutating func merge(_ other: [String: Any]) {
for (key, value) in other {
if let stringValue = value as? String {
queryParams[key] = stringValue
} else {
extra[key] = value
}
}
}
}
// ═══════════════════════════════════════════════════════════════════
// MARK: - URLRouter 核心实现
// ═══════════════════════════════════════════════════════════════════
public final class URLRouter {
public static let shared = URLRouter()
// MARK: - Properties
/// 路由表:path pattern -> handler
private var routeMap: [String: (URL, RouteParams) -> RouteResult] = [:]
/// 正则路由表(支持更复杂的匹配)
private var regexRouteMap: [(NSRegularExpression, (URL, RouteParams) -> RouteResult)] = []
/// 拦截器列表
private var interceptors: [RouteInterceptor] = []
/// 全局路由监听
private var globalListeners: [(URL, RouteParams) -> Void] = []
/// 404处理器
private var notFoundHandler: ((URL) -> UIViewController)?
/// 错误处理器
private var errorHandler: ((RouteError, URL) -> Void)?
/// URL Scheme
private let scheme: String
/// 线程安全锁
private let lock = NSRecursiveLock()
// MARK: - Initialization
public init(scheme: String = "myapp") {
self.scheme = scheme
}
// ═══════════════════════════════════════════════════════════════
// MARK: - 路由注册
// ═══════════════════════════════════════════════════════════════
/// 注册页面路由(返回ViewController)
@discardableResult
public func register(
_ pattern: String,
handler: @escaping (URL, RouteParams) -> UIViewController?
) -> Self {
let normalizedPattern = normalizePattern(pattern)
lock.lock()
defer { lock.unlock() }
routeMap[normalizedPattern] = { url, params in
if let vc = handler(url, params) {
return .viewController(vc)
}
return .failed(.notFound(path: pattern))
}
print("🛣️ [Router] Registered: (normalizedPattern)")
return self
}
/// 注册动作路由(执行闭包)
@discardableResult
public func registerAction(
_ pattern: String,
handler: @escaping (URL, RouteParams) -> Void
) -> Self {
let normalizedPattern = normalizePattern(pattern)
lock.lock()
defer { lock.unlock() }
routeMap[normalizedPattern] = { url, params in
return .handler { handler(url, params) }
}
print("⚡ [Router] Registered Action: (normalizedPattern)")
return self
}
/// 注册正则路由
@discardableResult
public func registerRegex(
_ pattern: String,
handler: @escaping (URL, RouteParams) -> UIViewController?
) -> Self {
guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else {
print("❌ [Router] Invalid regex pattern: (pattern)")
return self
}
lock.lock()
defer { lock.unlock() }
regexRouteMap.append((regex, { url, params in
if let vc = handler(url, params) {
return .viewController(vc)
}
return .failed(.notFound(path: pattern))
}))
print("🔤 [Router] Registered Regex: (pattern)")
return self
}
/// 注册重定向
@discardableResult
public func registerRedirect(from source: String, to destination: String) -> Self {
let normalizedSource = normalizePattern(source)
lock.lock()
defer { lock.unlock() }
routeMap[normalizedSource] = { [weak self] url, params in
guard let destURL = self?.buildURL(destination, params: params) else {
return .failed(.notFound(path: destination))
}
return .redirect(destURL)
}
print("↪️ [Router] Registered Redirect: (source) -> (destination)")
return self
}
/// 批量注册
@discardableResult
public func registerBatch(
_ routes: [(String, (URL, RouteParams) -> UIViewController?)]
) -> Self {
for (pattern, handler) in routes {
register(pattern, handler: handler)
}
return self
}
// ═══════════════════════════════════════════════════════════════
// MARK: - 路由导航
// ═══════════════════════════════════════════════════════════════
/// 通过URL字符串导航
public func open(
_ urlString: String,
from sourceVC: UIViewController? = nil,
options: RouteOptions = .default,
extra: [String: Any]? = nil
) {
guard let url = URL(string: urlString) else {
print("❌ [Router] Invalid URL: (urlString)")
return
}
open(url: url, from: sourceVC, options: options, extra: extra)
}
/// 通过URL对象导航
public func open(
url: URL,
from sourceVC: UIViewController? = nil,
options: RouteOptions = .default,
extra: [String: Any]? = nil
) {
// 解析参数
var params = parseParams(from: url)
if let extra = extra {
params.merge(extra)
}
params.sourceURL = url
print("🔍 [Router] Opening: (url.absoluteString)")
// 通知全局监听器
notifyListeners(url: url, params: params)
// 执行拦截器链
runInterceptors(url: url, params: params) { [weak self] result in
switch result {
case .continue:
self?.performRouting(url: url, params: params,
from: sourceVC, options: options)
case .redirect(let newURL):
self?.open(url: newURL, from: sourceVC, options: options)
case .reject(let error):
self?.handleError(error, for: url)
case .handled:
print("✅ [Router] Handled by interceptor")
}
}
}
/// 获取目标ViewController(不进行导航)
public func viewController(for urlString: String,
extra: [String: Any]? = nil) -> UIViewController? {
guard let url = URL(string: urlString) else { return nil }
return viewController(for: url, extra: extra)
}
public func viewController(for url: URL,
extra: [String: Any]? = nil) -> UIViewController? {
var params = parseParams(from: url)
if let extra = extra {
params.merge(extra)
}
let result = matchRoute(url: url, params: params)
switch result {
case .viewController(let vc):
return vc
default:
return nil
}
}
/// 检查URL是否可以被处理
public func canOpen(_ urlString: String) -> Bool {
guard let url = URL(string: urlString) else { return false }
return canOpen(url: url)
}
public func canOpen(url: URL) -> Bool {
let params = parseParams(from: url)
let result = matchRoute(url: url, params: params)
if case .failed = result {
return false
}
return true
}
// ═══════════════════════════════════════════════════════════════
// MARK: - 拦截器管理
// ═══════════════════════════════════════════════════════════════
/// 添加拦截器
public func addInterceptor(_ interceptor: RouteInterceptor) {
lock.lock()
defer { lock.unlock() }
interceptors.append(interceptor)
// 按优先级排序
interceptors.sort { $0.priority > $1.priority }
}
/// 移除拦截器
public func removeInterceptor<T: RouteInterceptor>(_ type: T.Type) {
lock.lock()
defer { lock.unlock() }
interceptors.removeAll { $0 is T }
}
// ═══════════════════════════════════════════════════════════════
// MARK: - 配置
// ═══════════════════════════════════════════════════════════════
/// 设置404处理器
public func setNotFoundHandler(_ handler: @escaping (URL) -> UIViewController) {
notFoundHandler = handler
}
/// 设置错误处理器
public func setErrorHandler(_ handler: @escaping (RouteError, URL) -> Void) {
errorHandler = handler
}
/// 添加全局路由监听
public func addListener(_ listener: @escaping (URL, RouteParams) -> Void) {
lock.lock()
defer { lock.unlock() }
globalListeners.append(listener)
}
// ═══════════════════════════════════════════════════════════════
// MARK: - Private Methods
// ═══════════════════════════════════════════════════════════════
/// 标准化路由模式
private func normalizePattern(_ pattern: String) -> String {
var result = pattern.lowercased()
if !result.hasPrefix("/") {
result = "/" + result
}
if result.hasSuffix("/") && result.count > 1 {
result.removeLast()
}
return result
}
/// 解析URL参数
private func parseParams(from url: URL) -> RouteParams {
var params = RouteParams()
// 解析Query参数
if let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
let queryItems = components.queryItems {
for item in queryItems {
params.queryParams[item.name] = item.value ?? ""
}
}
return params
}
/// 匹配路由
private func matchRoute(url: URL, params: RouteParams) -> RouteResult {
let path = normalizePattern(url.path)
var mutableParams = params
// 1. 精确匹配
if let handler = routeMap[path] {
return handler(url, mutableParams)
}
// 2. 模式匹配 (支持 :param 占位符)
for (pattern, handler) in routeMap {
if let pathParams = matchPattern(path: path, pattern: pattern) {
mutableParams.pathParams = pathParams
return handler(url, mutableParams)
}
}
// 3. 正则匹配
for (regex, handler) in regexRouteMap {
let range = NSRange(path.startIndex..., in: path)
if let match = regex.firstMatch(in: path, options: [], range: range) {
// 提取正则捕获组
for i in 1..<match.numberOfRanges {
if let range = Range(match.range(at: i), in: path) {
mutableParams.pathParams["group(i)"] = String(path[range])
}
}
return handler(url, mutableParams)
}
}
return .failed(.notFound(path: path))
}
/// 模式匹配(支持 /product/:id/:type 这种格式)
private func matchPattern(path: String, pattern: String) -> [String: String]? {
let pathComponents = path.split(separator: "/")
let patternComponents = pattern.split(separator: "/")
guard pathComponents.count == patternComponents.count else { return nil }
var params: [String: String] = [:]
for (pathPart, patternPart) in zip(pathComponents, patternComponents) {
if patternPart.hasPrefix(":") {
// 参数占位符
let paramName = String(patternPart.dropFirst())
params[paramName] = String(pathPart)
} else if patternPart == "*" {
// 通配符
continue
} else if pathPart.lowercased() != patternPart.lowercased() {
return nil
}
}
return params
}
/// 执行拦截器链
private func runInterceptors(
url: URL,
params: RouteParams,
completion: @escaping (InterceptResult) -> Void
) {
let applicableInterceptors = interceptors.filter { $0.shouldIntercept(url: url, params: params) }
guard !applicableInterceptors.isEmpty else {
completion(.continue)
return
}
// 递归执行拦截器
runNextInterceptor(
interceptors: applicableInterceptors,
index: 0,
url: url,
params: params,
completion: completion
)
}
private func runNextInterceptor(
interceptors: [RouteInterceptor],
index: Int,
url: URL,
params: RouteParams,
completion: @escaping (InterceptResult) -> Void
) {
guard index < interceptors.count else {
completion(.continue)
return
}
let interceptor = interceptors[index]
interceptor.intercept(url: url, params: params) { [weak self] result in
switch result {
case .continue:
// 继续下一个拦截器
self?.runNextInterceptor(
interceptors: interceptors,
index: index + 1,
url: url,
params: params,
completion: completion
)
default:
completion(result)
}
}
}
/// 执行路由
private func performRouting(
url: URL,
params: RouteParams,
from sourceVC: UIViewController?,
options: RouteOptions
) {
let result = matchRoute(url: url, params: params)
DispatchQueue.main.async { [weak self] in
switch result {
case .viewController(let targetVC):
self?.navigate(to: targetVC, from: sourceVC, options: options)
case .handler(let action):
action()
options.completion?()
case .redirect(let newURL):
self?.open(url: newURL, from: sourceVC, options: options)
case .failed(let error):
self?.handleError(error, for: url)
if let notFoundVC = self?.notFoundHandler?(url) {
self?.navigate(to: notFoundVC, from: sourceVC, options: options)
}
}
}
}
/// 执行导航
private func navigate(
to targetVC: UIViewController,
from sourceVC: UIViewController?,
options: RouteOptions
) {
let source = sourceVC ?? topViewController()
guard let source = source else {
print("❌ [Router] No source view controller")
return
}
switch options.navigationType {
case .push:
if let nav = source.navigationController {
nav.pushViewController(targetVC, animated: options.animated)
} else {
source.present(targetVC, animated: options.animated,
completion: options.completion)
}
case .present:
source.present(targetVC, animated: options.animated,
completion: options.completion)
case .presentFullScreen:
targetVC.modalPresentationStyle = .fullScreen
source.present(targetVC, animated: options.animated,
completion: options.completion)
case .replace:
if let nav = source.navigationController {
var viewControllers = nav.viewControllers
viewControllers.removeLast()
viewControllers.append(targetVC)
nav.setViewControllers(viewControllers, animated: options.animated)
}
case .root:
if let nav = source.navigationController {
nav.setViewControllers([targetVC], animated: options.animated)
} else if let window = source.view.window {
window.rootViewController = targetVC
}
case .custom(let navigator):
navigator(source, targetVC)
}
}
/// 构建URL
private func buildURL(_ path: String, params: RouteParams) -> URL? {
var components = URLComponents()
components.scheme = scheme
components.path = path
if !params.queryParams.isEmpty {
components.queryItems = params.queryParams.map {
URLQueryItem(name: $0.key, value: $0.value)
}
}
return components.url
}
/// 获取顶层ViewController
private func topViewController() -> UIViewController? {
guard let window = UIApplication.shared.windows.first(where: { $0.isKeyWindow }),
let rootVC = window.rootViewController else {
return nil
}
return findTopViewController(from: rootVC)
}
private func findTopViewController(from vc: UIViewController) -> UIViewController {
if let presented = vc.presentedViewController {
return findTopViewController(from: presented)
}
if let nav = vc as? UINavigationController,
let topVC = nav.topViewController {
return findTopViewController(from: topVC)
}
if let tab = vc as? UITabBarController,
let selectedVC = tab.selectedViewController {
return findTopViewController(from: selectedVC)
}
return vc
}
/// 通知监听器
private func notifyListeners(url: URL, params: RouteParams) {
for listener in globalListeners {
listener(url, params)
}
}
/// 错误处理
private func handleError(_ error: RouteError, for url: URL) {
print("❌ [Router] Error: (error.localizedDescription)")
errorHandler?(error, url)
}
}
常用拦截器实现
// MARK: - 登录拦截器
public final class LoginInterceptor: RouteInterceptor {
public var priority: Int { return 100 }
/// 需要登录的路径
private var protectedPatterns: [String] = [
"/order",
"/orders",
"/cart",
"/checkout",
"/user/settings",
"/user/address",
"/wallet"
]
/// 白名单路径(即使在受保护目录下也不需要登录)
private var whitelistPatterns: [String] = [
"/user/login",
"/user/register",
"/user/forgot-password"
]
private let userService: UserServiceProtocol
public init(userService: UserServiceProtocol) {
self.userService = userService
}
public func shouldIntercept(url: URL, params: RouteParams) -> Bool {
let path = url.path.lowercased()
// 白名单放行
if whitelistPatterns.contains(where: { path.hasPrefix($0) }) {
return false
}
// 检查是否需要登录
return protectedPatterns.contains(where: { path.hasPrefix($0) })
}
public func intercept(url: URL, params: RouteParams,
completion: @escaping (InterceptResult) -> Void) {
if userService.isLoggedIn() {
completion(.continue)
} else {
// 保存目标URL
savePendingURL(url)
// 重定向到登录页
let loginURL = URL(string: "myapp:///user/login?redirect=(url.absoluteString.urlEncoded)")!
completion(.redirect(loginURL))
}
}
/// 添加受保护路径
public func addProtectedPattern(_ pattern: String) {
protectedPatterns.append(pattern)
}
/// 添加白名单路径
public func addWhitelistPattern(_ pattern: String) {
whitelistPatterns.append(pattern)
}
private func savePendingURL(_ url: URL) {
UserDefaults.standard.set(url.absoluteString, forKey: "pending_route_url")
}
/// 登录成功后调用,跳转到之前保存的URL
public func resumePendingRoute() {
guard let urlString = UserDefaults.standard.string(forKey: "pending_route_url"),
let url = URL(string: urlString) else {
return
}
UserDefaults.standard.removeObject(forKey: "pending_route_url")
URLRouter.shared.open(url: url)
}
}
extension String {
var urlEncoded: String {
return addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? self
}
}
// MARK: - 降级拦截器
public final class DegradeInterceptor: RouteInterceptor {
public var priority: Int { return 90 }
/// 降级配置(可从服务器动态获取)
private var degradeConfig: [String: DegradeAction] = [:]
public enum DegradeAction {
case block(message: String) // 阻止访问
case redirect(url: String) // 重定向
case webView(url: String) // 降级到H5
case toast(message: String) // 提示后继续
}
public init() {}
/// 更新降级配置
public func updateConfig(_ config: [String: DegradeAction]) {
self.degradeConfig = config
}
public func shouldIntercept(url: URL, params: RouteParams) -> Bool {
let path = url.path.lowercased()
return degradeConfig.keys.contains { path.hasPrefix($0) }
}
public func intercept(url: URL, params: RouteParams,
completion: @escaping (InterceptResult) -> Void) {
let path = url.path.lowercased()
// 查找匹配的降级配置
guard let (_, action) = degradeConfig.first(where: { path.hasPrefix($0.key) }) else {
completion(.continue)
return
}
switch action {
case .block(let message):
showToast(message)
completion(.handled)
case .redirect(let urlString):
if let redirectURL = URL(string: urlString) {
completion(.redirect(redirectURL))
} else {
completion(.continue)
}
case .webView(let webURLString):
let h5URL = URL(string: "myapp:///webview?url=(webURLString.urlEncoded)")!
completion(.redirect(h5URL))
case .toast(let message):
showToast(message)
completion(.continue)
}
}
private func showToast(_ message: String) {
// 显示Toast提示
print("📢 [Degrade] (message)")
}
}
// MARK: - 统计埋点拦截器
public final class AnalyticsInterceptor: RouteInterceptor {
public var priority: Int { return 50 }
private let analyticsService: AnalyticsServiceProtocol
public init(analyticsService: AnalyticsServiceProtocol) {
self.analyticsService = analyticsService
}
public func shouldIntercept(url: URL, params: RouteParams) -> Bool {
// 所有路由都需要埋点
return true
}
public func intercept(url: URL, params: RouteParams,
completion: @escaping (InterceptResult) -> Void) {
// 异步埋点,不阻塞路由
analyticsService.trackPageView(
path: url.path,
params: params.queryParams,
source: params.queryParams["source"]
)
// 继续路由
completion(.continue)
}
}
// MARK: - AB测试拦截器
public final class ABTestInterceptor: RouteInterceptor {
public var priority: Int { return 80 }
/// AB测试配置
private var experiments: [String: ABExperiment] = [:]
public struct ABExperiment {
let originalPath: String
let variants: [String: String] // variantName -> redirectPath
let distribution: [String: Double] // variantName -> percentage
}
public init() {}
public func updateExperiments(_ experiments: [String: ABExperiment]) {
self.experiments = experiments
}
public func shouldIntercept(url: URL, params: RouteParams) -> Bool {
return experiments.keys.contains(url.path)
}
public func intercept(url: URL, params: RouteParams,
completion: @escaping (InterceptResult) -> Void) {
guard let experiment = experiments[url.path] else {
completion(.continue)
return
}
// 决定用户分组
let variant = determineVariant(for: experiment)
if let redirectPath = experiment.variants[variant],
let redirectURL = URL(string: "myapp://(redirectPath)") {
completion(.redirect(redirectURL))
} else {
completion(.continue)
}
}
private func determineVariant(for experiment: ABExperiment) -> String {
// 根据用户ID或设备ID决定分组,保证同一用户每次分组一致
let random = Double.random(in: 0...1)
var cumulative: Double = 0
for (variant, percentage) in experiment.distribution {
cumulative += percentage
if random <= cumulative {
return variant
}
}
return "control"
}
}
路由注册与使用示例
// MARK: - 模块路由注册
/// 商品模块路由注册
extension ProductModule {
static func registerRoutes(router: URLRouter) {
// 商品详情页
router.register("/product/:id") { url, params in
guard let productId = params.string("id") else { return nil }
return ProductDetailViewController(productId: productId)
}
// 商品列表页
router.register("/products") { url, params in
let category = params.string("category")
let sort = params.string("sort")
return ProductListViewController(category: category, sortType: sort)
}
// 商品搜索页
router.register("/search") { url, params in
let keyword = params.string("keyword")
return SearchViewController(keyword: keyword)
}
// 商品分类页
router.register("/category/:id") { url, params in
guard let categoryId = params.string("id") else { return nil }
return CategoryViewController(categoryId: categoryId)
}
// 品牌页
router.register("/brand/:id") { url, params in
guard let brandId = params.string("id") else { return nil }
return BrandViewController(brandId: brandId)
}
}
}
/// 订单模块路由注册
extension OrderModule {
static func registerRoutes(router: URLRouter) {
// 订单列表
router.register("/orders") { url, params in
let status = params.string("status")
return OrderListViewController(statusFilter: status)
}
// 订单详情
router.register("/order/:id") { url, params in
guard let orderId = params.string("id") else { return nil }
return OrderDetailViewController(orderId: orderId)
}
// 订单确认页
router.register("/checkout") { url, params in
// 从extra获取复杂对象
let cartItems: [CartItem]? = params.get("cartItems")
return CheckoutViewController(items: cartItems ?? [])
}
// 支付结果页
router.register("/payment/result") { url, params in
let orderId = params.string("orderId") ?? ""
let success = params.bool("success")
return PaymentResultViewController(orderId: orderId, success: success)
}
}
}
/// WebView路由注册
extension WebViewModule {
static func registerRoutes(router: URLRouter) {
// 通用WebView
router.register("/webview") { url, params in
guard let urlString = params.string("url"),
let webURL = URL(string: urlString) else {
return nil
}
let title = params.string("title")
return WebViewController(url: webURL, title: title)
}
// 帮助中心
router.register("/help") { url, params in
let webURL = URL(string: "https://help.yourcompany.com")!
return WebViewController(url: webURL, title: "帮助中心")
}
// 隐私政策
router.register("/privacy") { url, params in
let webURL = URL(string: "https://yourcompany.com/privacy")!
return WebViewController(url: webURL, title: "隐私政策")
}
}
}
// MARK: - 动作路由注册
extension ActionRoutes {
static func registerRoutes(router: URLRouter) {
// 分享动作
router.registerAction("/action/share") { url, params in
guard let title = params.string("title"),
let shareURL = params.string("url") else { return }
ShareManager.share(title: title, url: shareURL)
}
// 拨打电话
router.registerAction("/action/call") { url, params in
guard let phone = params.string("phone"),
let phoneURL = URL(string: "tel://(phone)") else { return }
UIApplication.shared.open(phoneURL)
}
// 复制到剪贴板
router.registerAction("/action/copy") { url, params in
guard let text = params.string("text") else { return }
UIPasteboard.general.string = text
Toast.show("已复制到剪贴板")
}
// 显示弹窗
router.registerAction("/action/alert") { url, params in
let title = params.string("title") ?? ""
let message = params.string("message") ?? ""
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "确定", style: .default))
UIApplication.topViewController()?.present(alert, animated: true)
}
}
}
// MARK: - 使用示例
class SomeViewController: UIViewController {
private let router = URLRouter.shared
// 方式1: 直接使用URL字符串
func openProductDetail() {
router.open("/product/12345")
}
// 方式2: 带查询参数
func openProductList() {
router.open("/products?category=phone&sort=price_asc")
}
// 方式3: 带额外参数(传递复杂对象)
func openCheckout(items: [CartItem]) {
router.open("/checkout", extra: ["cartItems": items])
}
// 方式4: 指定导航方式
func presentLogin() {
router.open("/user/login",
options: RouteOptions(navigationType: .presentFullScreen))
}
// 方式5: 带完成回调
func openSetting() {
router.open("/user/settings",
options: RouteOptions(navigationType: .push) {
print("设置页已打开")
})
}
// 方式6: 获取ViewController后自定义处理
func customNavigation() {
if let vc = router.viewController(for: "/product/123") {
// 自定义转场动画等
let transition = CATransition()
transition.type = .fade
navigationController?.view.layer.add(transition, forKey: nil)
navigationController?.pushViewController(vc, animated: false)
}
}
// 方式7: 检查是否可以跳转
func checkAndOpen() {
let url = "/some/path"
if router.canOpen(url) {
router.open(url)
} else {
print("该路由不可用")
}
}
}
Target-Action 方案
CTMediator 风格实现
// MARK: - Mediator 核心实现
/// 组件中间件(CTMediator 风格)
public final class Mediator {
public static let shared = Mediator()
/// Target实例缓存
private var targetCache: [String: NSObject] = [:]
/// 缓存锁
private let lock = NSLock()
private init() {}
// ═══════════════════════════════════════════════════════════════
// MARK: - 核心调用方法
// ═══════════════════════════════════════════════════════════════
/// 远程调用(URL方式)
@discardableResult
public func performAction(url: URL) -> Any? {
guard let host = url.host else { return nil }
// URL格式:myapp://target/action?params
let pathComponents = url.pathComponents.filter { $0 != "/" }
guard let actionName = pathComponents.first else { return nil }
// 解析参数
var params: [String: Any] = [:]
if let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
let queryItems = components.queryItems {
for item in queryItems {
params[item.name] = item.value ?? ""
}
}
return performTarget(host, action: actionName, params: params, shouldCacheTarget: false)
}
/// 本地调用
@discardableResult
public func performTarget(
_ targetName: String,
action actionName: String,
params: [String: Any]? = nil,
shouldCacheTarget: Bool = false
) -> Any? {
// 1. 构造 Target 类名 (Swift需要带模块名)
let targetClassString = "Target_(targetName)"
let swiftTargetClassString = "(Bundle.main.infoDictionary?["CFBundleName"] as? String ?? "").(targetClassString)"
// 2. 获取或创建 Target 实例
var target: NSObject?
if shouldCacheTarget {
lock.lock()
target = targetCache[targetClassString]
lock.unlock()
}
if target == nil {
// 尝试 Swift 类名
if let targetClass = NSClassFromString(swiftTargetClassString) as? NSObject.Type {
target = targetClass.init()
}
// 尝试 OC 类名
else if let targetClass = NSClassFromString(targetClassString) as? NSObject.Type {
target = targetClass.init()
}
}
guard let targetInstance = target else {
print("❌ [Mediator] Target not found: (targetClassString)")
return notFoundAction(targetName: targetName, actionName: actionName)
}
// 缓存Target
if shouldCacheTarget {
lock.lock()
targetCache[targetClassString] = targetInstance
lock.unlock()
}
// 3. 构造 Action 选择器
let actionString = "Action_(actionName):"
let selector = NSSelectorFromString(actionString)
// 4. 检查是否响应该方法
guard targetInstance.responds(to: selector) else {
print("❌ [Mediator] Action not found: (actionString)")
return notFoundAction(targetName: targetName, actionName: actionName)
}
// 5. 执行调用
print("📞 [Mediator] Calling: (targetClassString).(actionString)")
return targetInstance.perform(selector, with: params)?.takeUnretainedValue()
}
/// 清理缓存的Target
public func clearCache() {
lock.lock()
targetCache.removeAll()
lock.unlock()
}
/// 移除指定Target缓存
public func removeTarget(_ targetName: String) {
let key = "Target_(targetName)"
lock.lock()
targetCache.removeValue(forKey: key)
lock.unlock()
}
// MARK: - Not Found Handler
private func notFoundAction(targetName: String, actionName: String) -> UIViewController {
let vc = UIViewController()
vc.view.backgroundColor = .systemBackground
let label = UILabel()
label.text = "页面走丢了~
(targetName)/(actionName)"
label.textAlignment = .center
label.numberOfLines = 0
label.textColor = .secondaryLabel
label.translatesAutoresizingMaskIntoConstraints = false
vc.view.addSubview(label)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: vc.view.centerXAnchor),
label.centerYAnchor.constraint(equalTo: vc.view.centerYAnchor),
label.leadingAnchor.constraint(greaterThanOrEqualTo: vc.view.leadingAnchor, constant: 20)
])
return vc
}
}
Target 实现示例
// MARK: - 商品模块 Target
/// Target 命名规范:Target_模块名
/// Action 命名规范:Action_操作名:
@objc class Target_Product: NSObject {
/// 获取商品详情页
@objc func Action_DetailViewController(_ params: [String: Any]) -> UIViewController {
guard let productId = params["productId"] as? String else {
return createErrorViewController("商品ID不能为空")
}
let vc = ProductDetailViewController(productId: productId)
// 可选参数
if let source = params["source"] as? String {
vc.source = source
}
return vc
}
/// 获取商品列表页
@objc func Action_ListViewController(_ params: [String: Any]) -> UIViewController {
let category = params["category"] as? String
let sortType = params["sortType"] as? String
return ProductListViewController(category: category, sortType: sortType)
}
/// 获取搜索页
@objc func Action_SearchViewController(_ params: [String: Any]) -> UIViewController {
let keyword = params["keyword"] as? String
return SearchViewController(keyword: keyword)
}
/// 获取商品数据(非页面调用)
@objc func Action_FetchProductInfo(_ params: [String: Any]) -> [String: Any]? {
guard let productId = params["productId"] as? String else {
return nil
}
// 同步获取商品信息(实际应该是从缓存获取)
return ProductCache.shared.getProduct(id: productId)?.toDictionary()
}
/// 添加商品到购物车(动作调用)
@objc func Action_AddToFavorite(_ params: [String: Any]) -> NSNumber {
guard let productId = params["productId"] as? String else {
return NSNumber(value: false)
}
let success = FavoriteManager.shared.add(productId: productId)
return NSNumber(value: success)
}
// MARK: - Helper
private func createErrorViewController(_ message: String) -> UIViewController {
let vc = UIViewController()
vc.view.backgroundColor = .systemBackground
let label = UILabel()
label.text = message
label.textColor = .systemRed
label.translatesAutoresizingMaskIntoConstraints = false
vc.view.addSubview(label)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: vc.view.centerXAnchor),
label.centerYAnchor.constraint(equalTo: vc.view.centerYAnchor)
])
return vc
}
}
// MARK: - 购物车模块 Target
@objc class Target_Cart: NSObject {
/// 获取购物车页面
@objc func Action_ViewController(_ params: [String: Any]) -> UIViewController {
return CartViewController()
}
/// 添加商品到购物车
@objc func Action_AddItem(_ params: [String: Any]) -> NSNumber {
guard let productId = params["productId"] as? String else {
return NSNumber(value: false)
}
let quantity = (params["quantity"] as? Int) ?? 1
do {
try CartManager.shared.addItem(productId: productId, quantity: quantity)
return NSNumber(value: true)
} catch {
return NSNumber(value: false)
}
}
/// 获取购物车数量
@objc func Action_GetItemCount(_ params: [String: Any]) -> NSNumber {
let count = CartManager.shared.totalItemCount
return NSNumber(value: count)
}
/// 获取购物车商品列表(返回可编码的字典数组)
@objc func Action_GetItems(_ params: [String: Any]) -> [[String: Any]] {
let items = CartManager.shared.items
return items.map { $0.toDictionary() }
}
}
// MARK: - 用户模块 Target
@objc class Target_User: NSObject {
/// 获取登录页面
@objc func Action_LoginViewController(_ params: [String: Any]) -> UIViewController {
let loginVC = LoginViewController()
// 设置登录成功回调
if let callback = params["callback"] as? ((Bool) -> Void) {
loginVC.onComplete = callback
}
let nav = UINavigationController(rootViewController: loginVC)
nav.modalPresentationStyle = .fullScreen
return nav
}
/// 获取用户中心页面
@objc func Action_CenterViewController(_ params: [String: Any]) -> UIViewController {
return UserCenterViewController()
}
/// 检查登录状态
@objc func Action_IsLoggedIn(_ params: [String: Any]) -> NSNumber {
return NSNumber(value: UserManager.shared.isLoggedIn)
}
/// 获取当前用户信息
@objc func Action_CurrentUserInfo(_ params: [String: Any]) -> [String: Any]? {
return UserManager.shared.currentUser?.toDictionary()
}
/// 登出
@objc func Action_Logout(_ params: [String: Any]) -> NSNumber {
UserManager.shared.logout()
return NSNumber(value: true)
}
}
// MARK: - 订单模块 Target
@objc class Target_Order: NSObject {
/// 订单列表页
@objc func Action_ListViewController(_ params: [String: Any]) -> UIViewController {
let status = params["status"] as? String
return OrderListViewController(statusFilter: status)
}
/// 订单详情页
@objc func Action_DetailViewController(_ params: [String: Any]) -> UIViewController {
guard let orderId = params["orderId"] as? String else {
return createErrorViewController("订单ID不能为空")
}
return OrderDetailViewController(orderId: orderId)
}
/// 结算页
@objc func Action_CheckoutViewController(_ params: [String: Any]) -> UIViewController {
// 支持传入CartItem对象数组
if let items = params["items"] as? [CartItem] {
return CheckoutViewController(items: items)
}
// 或者传入商品ID和数量
if let productId = params["productId"] as? String,
let quantity = params["quantity"] as? Int {
return CheckoutViewController(productId: productId, quantity: quantity)
}
return createErrorViewController("参数错误")
}
private func createErrorViewController(_ message: String) -> UIViewController {
let vc = UIViewController()
vc.title = "错误"
let label = UILabel()
label.text = message
label.textColor = .systemRed
vc.view.addSubview(label)
return vc
}
}
类型安全的 Mediator 扩展
// MARK: - 类型安全的扩展(推荐使用方式)
/// 商品模块扩展
public extension Mediator {
/// 商品详情页
func productDetailViewController(productId: String, source: String? = nil) -> UIViewController {
var params: [String: Any] = ["productId": productId]
if let source = source {
params["source"] = source
}
return performTarget("Product", action: "DetailViewController",
params: params) as? UIViewController
?? UIViewController()
}
/// 商品列表页
func productListViewController(category: String? = nil,
sortType: String? = nil) -> UIViewController {
var params: [String: Any] = [:]
if let category = category { params["category"] = category }
if let sortType = sortType { params["sortType"] = sortType }
return performTarget("Product", action: "ListViewController",
params: params) as? UIViewController
?? UIViewController()
}
/// 搜索页
func searchViewController(keyword: String? = nil) -> UIViewController {
let params: [String: Any] = keyword != nil ? ["keyword": keyword!] : [:]
return performTarget("Product", action: "SearchViewController",
params: params) as? UIViewController
?? UIViewController()
}
/// 获取商品信息
func fetchProductInfo(productId: String) -> [String: Any]? {
return performTarget("Product", action: "FetchProductInfo",
params: ["productId": productId]) as? [String: Any]
}
/// 添加收藏
@discardableResult
func addToFavorite(productId: String) -> Bool {
let result = performTarget("Product", action: "AddToFavorite",
params: ["productId": productId])
return (result as? NSNumber)?.boolValue ?? false
}
}
/// 购物车模块扩展
public extension Mediator {
/// 购物车页面
func cartViewController() -> UIViewController {
return performTarget("Cart", action: "ViewController") as? UIViewController
?? UIViewController()
}
/// 添加商品到购物车
@discardableResult
func addToCart(productId: String, quantity: Int = 1) -> Bool {
let result = performTarget("Cart", action: "AddItem",
params: ["productId": productId, "quantity": quantity])
return (result as? NSNumber)?.boolValue ?? false
}
/// 获取购物车数量
func cartItemCount() -> Int {
let result = performTarget("Cart", action: "GetItemCount")
return (result as? NSNumber)?.intValue ?? 0
}
/// 获取购物车列表
func cartItems() -> [[String: Any]] {
return performTarget("Cart", action: "GetItems") as? [[String: Any]] ?? []
}
}
/// 用户模块扩展
public extension Mediator {
/// 登录页面
func loginViewController(completion: ((Bool) -> Void)? = nil) -> UIViewController {
var params: [String: Any] = [:]
if let completion = completion {
params["callback"] = completion
}
return performTarget("User", action: "LoginViewController",
params: params) as? UIViewController
?? UIViewController()
}
/// 用户中心
func userCenterViewController() -> UIViewController {
return performTarget("User", action: "CenterViewController") as? UIViewController
?? UIViewController()
}
/// 是否已登录
var isLoggedIn: Bool {
let result = performTarget("User", action: "IsLoggedIn")
return (result as? NSNumber)?.boolValue ?? false
}
/// 当前用户信息
func currentUserInfo() -> [String: Any]? {
return performTarget("User", action: "CurrentUserInfo") as? [String: Any]
}
/// 登出
func logout() {
performTarget("User", action: "Logout")
}
/// 如果需要登录,弹出登录页
func requireLogin(from viewController: UIViewController,
completion: @escaping (Bool) -> Void) {
if isLoggedIn {
completion(true)
return
}
let loginVC = loginViewController(completion: completion)
viewController.present(loginVC, animated: true)
}
}
/// 订单模块扩展
public extension Mediator {
/// 订单列表页
func orderListViewController(status: String? = nil) -> UIViewController {
let params: [String: Any] = status != nil ? ["status": status!] : [:]
return performTarget("Order", action: "ListViewController",
params: params) as? UIViewController
?? UIViewController()
}
/// 订单详情页
func orderDetailViewController(orderId: String) -> UIViewController {
return performTarget("Order", action: "DetailViewController",
params: ["orderId": orderId]) as? UIViewController
?? UIViewController()
}
/// 结算页(通过购物车商品)
func checkoutViewController(items: [CartItem]) -> UIViewController {
return performTarget("Order", action: "CheckoutViewController",
params: ["items": items]) as? UIViewController
?? UIViewController()
}
/// 结算页(直接购买)
func checkoutViewController(productId: String, quantity: Int) -> UIViewController {
return performTarget("Order", action: "CheckoutViewController",
params: ["productId": productId, "quantity": quantity]) as? UIViewController
?? UIViewController()
}
}
// MARK: - 使用示例
class ProductDetailViewController: UIViewController {
private let productId: String
var source: String?
init(productId: String) {
self.productId = productId
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// 添加到购物车
@objc private func addToCartTapped() {
// 方式1:通过扩展方法(类型安全,推荐)
let success = Mediator.shared.addToCart(productId: productId, quantity: 1)
if success {
showToast("添加成功")
updateCartBadge()
} else {
showToast("添加失败")
}
}
// 立即购买
@objc private func buyNowTapped() {
// 先检查登录状态
Mediator.shared.requireLogin(from: self) { [weak self] success in
guard success, let self = self else { return }
// 跳转结算页
let checkoutVC = Mediator.shared.checkoutViewController(
productId: self.productId,
quantity: 1
)
self.navigationController?.pushViewController(checkoutVC, animated: true)
}
}
// 查看购物车
@objc private func viewCartTapped() {
let cartVC = Mediator.shared.cartViewController()
navigationController?.pushViewController(cartVC, animated: true)
}
private func updateCartBadge() {
let count = Mediator.shared.cartItemCount()
// 更新TabBar角标
tabBarController?.tabBar.items?[2].badgeValue = count > 0 ? "(count)" : nil
}
private func showToast(_ message: String) {
// 显示Toast
}
}
Protocol-Class 方案
协议注册机制实现
// MARK: - Protocol-Class 路由实现
import Foundation
// ═══════════════════════════════════════════════════════════════════
// MARK: - 模块协议定义
// ═══════════════════════════════════════════════════════════════════
/// 模块协议基类
public protocol ModuleServiceProtocol: AnyObject {
/// 模块初始化
static func moduleDidLoad()
}
/// 商品模块协议
public protocol ProductModuleProtocol: ModuleServiceProtocol {
/// 商品详情页
func productDetailViewController(productId: String) -> UIViewController
/// 商品列表页
func productListViewController(category: String?) -> UIViewController
/// 搜索页
func searchViewController(keyword: String?) -> UIViewController
/// 获取商品信息
func fetchProduct(by id: String) async throws -> ProductInfo
/// 批量获取商品
func fetchProducts(ids: [String]) async throws -> [ProductInfo]
}
/// 购物车模块协议
public protocol CartModuleProtocol: ModuleServiceProtocol {
/// 购物车页面
func cartViewController() -> UIViewController
/// 添加商品
func addItem(productId: String, quantity: Int) async throws
/// 移除商品
func removeItem(itemId: String) async throws
/// 获取购物车列表
func fetchItems() async throws -> [CartItem]
/// 购物车数量
var itemCount: Int { get }
/// 数量变化通知
var itemCountPublisher: AnyPublisher<Int, Never> { get }
}
/// 用户模块协议
public protocol UserModuleProtocol: ModuleServiceProtocol {
/// 登录页
func loginViewController(completion: ((Bool) -> Void)?) -> UIViewController
/// 用户中心页
func userCenterViewController() -> UIViewController
/// 是否已登录
var isLoggedIn: Bool { get }
/// 当前用户
var currentUser: UserInfo? { get }
/// 登录
func login(phone: String, code: String) async throws -> UserInfo
/// 登出
func logout() async throws
/// 登录状态发布者
var loginStatePublisher: AnyPublisher<Bool, Never> { get }
}
/// 订单模块协议
public protocol OrderModuleProtocol: ModuleServiceProtocol {
/// 订单列表页
func orderListViewController(status: OrderStatus?) -> UIViewController
/// 订单详情页
func orderDetailViewController(orderId: String) -> UIViewController
/// 结算页
func checkoutViewController(items: [CartItem]) -> UIViewController
/// 创建订单
func createOrder(items: [CartItem], addressId: String) async throws -> OrderInfo
/// 取消订单
func cancelOrder(orderId: String) async throws
}
// ═══════════════════════════════════════════════════════════════════
// MARK: - 模块注册器
// ═══════════════════════════════════════════════════════════════════
/// 模块注册器(类似 BeeHive 的实现)
public final class ModuleManager {
public static let shared = ModuleManager()
/// 协议 -> 实现类 映射
private var protocolClassMap: [String: AnyClass] = [:]
/// 协议 -> 实例 映射(单例)
private var protocolInstanceMap: [String: Any] = [:]
/// 模块类列表
private var moduleClasses: [ModuleServiceProtocol.Type] = []
/// 是否已初始化
private var isInitialized = false
/// 线程锁
private let lock = NSRecursiveLock()
private init() {}
// ═══════════════════════════════════════════════════════════════
// MARK: - 注册
// ═══════════════════════════════════════════════════════════════
/// 注册协议与实现类的映射
public func register<Protocol, Implementation: AnyObject>(
protocol: Protocol.Type,
implementation: Implementation.Type
) {
lock.lock()
defer { lock.unlock() }
let protocolKey = String(describing: `protocol`)
protocolClassMap[protocolKey] = implementation
print("📝 [ModuleManager] Registered: (protocolKey) -> (implementation)")
}
/// 注册协议与实例的映射(单例模式)
public func register<Protocol>(
protocol: Protocol.Type,
instance: Protocol
) {
lock.lock()
defer { lock.unlock() }
let protocolKey = String(describing: `protocol`)
protocolInstanceMap[protocolKey] = instance
print("📝 [ModuleManager] Registered Instance: (protocolKey)")
}
/// 注册模块类
public func registerModule(_ moduleClass: ModuleServiceProtocol.Type) {
lock.lock()
defer { lock.unlock() }
moduleClasses.append(moduleClass)
print("📦 [ModuleManager] Module Registered: (moduleClass)")
}
/// 批量注册模块
public func registerModules(_ classes: [ModuleServiceProtocol.Type]) {
for moduleClass in classes {
registerModule(moduleClass)
}
}
// ═══════════════════════════════════════════════════════════════
// MARK: - 获取服务
// ═══════════════════════════════════════════════════════════════
/// 获取协议对应的服务实例
public func service<Protocol>(for protocol: Protocol.Type) -> Protocol? {
lock.lock()
defer { lock.unlock() }
let protocolKey = String(describing: `protocol`)
// 1. 先从实例缓存获取
if let instance = protocolInstanceMap[protocolKey] as? Protocol {
return instance
}
// 2. 从类映射创建实例
if let implClass = protocolClassMap[protocolKey] as? NSObject.Type {
let instance = implClass.init()
// 缓存实例
protocolInstanceMap[protocolKey] = instance
if let service = instance as? Protocol {
return service
}
}
print("⚠️ [ModuleManager] Service not found for: (protocolKey)")
return nil
}
/// 获取服务(强制解包)
public func serviceRequired<Protocol>(for protocol: Protocol.Type) -> Protocol {
guard let service = service(for: `protocol`) else {
fatalError("❌ [ModuleManager] Required service not found: (String(describing: `protocol`))")
}
return service
}
// ═══════════════════════════════════════════════════════════════
// MARK: - 初始化
// ═══════════════════════════════════════════════════════════════
/// 初始化所有模块
public func initializeAllModules() {
lock.lock()
defer { lock.unlock() }
guard !isInitialized else { return }
print("🚀 [ModuleManager] Initializing (moduleClasses.count) modules...")
for moduleClass in moduleClasses {
moduleClass.moduleDidLoad()
print("✅ [ModuleManager] Initialized: (moduleClass)")
}
isInitialized = true
print("🎉 [ModuleManager] All modules initialized!")
}
/// 检查服务是否已注册
public func isRegistered<Protocol>(for protocol: Protocol.Type) -> Bool {
let protocolKey = String(describing: `protocol`)
return protocolInstanceMap[protocolKey] != nil || protocolClassMap[protocolKey] != nil
}
}
// ═══════════════════════════════════════════════════════════════════
// MARK: - 属性包装器
// ═══════════════════════════════════════════════════════════════════
/// 服务自动注入
@propertyWrapper
public struct ModuleService<Service> {
private var service: Service?
public init() {}
public var wrappedValue: Service {
mutating get {
if service == nil {
service = ModuleManager.shared.service(for: Service.self)
}
guard let service = service else {
fatalError("❌ Service (Service.self) not registered")
}
return service
}
}
}
/// 可选服务注入
@propertyWrapper
public struct OptionalModuleService<Service> {
public var wrappedValue: Service?
public init() {
self.wrappedValue = ModuleManager.shared.service(for: Service.self)
}
}
模块实现示例
// MARK: - 商品模块实现
/// 商品模块实现类
public final class ProductModuleImpl: NSObject, ProductModuleProtocol {
private let networkService: NetworkServiceProtocol
private let cacheService: CacheServiceProtocol
public override init() {
// 从ModuleManager获取依赖服务
self.networkService = ModuleManager.shared.serviceRequired(for: NetworkServiceProtocol.self)
self.cacheService = ModuleManager.shared.serviceRequired(for: CacheServiceProtocol.self)
super.init()
}
// MARK: - ModuleServiceProtocol
public static func moduleDidLoad() {
print("📦 [ProductModule] Module loaded")
// 注册自己
ModuleManager.shared.register(
protocol: ProductModuleProtocol.self,
implementation: ProductModuleImpl.self
)
// 预加载数据等
Task {
await preloadData()
}
}
private static func preloadData() async {
// 预加载商品分类等
}
// MARK: - ProductModuleProtocol
public func productDetailViewController(productId: String) -> UIViewController {
return ProductDetailViewController(productId: productId)
}
public func productListViewController(category: String?) -> UIViewController {
return ProductListViewController(category: category)
}
public func searchViewController(keyword: String?) -> UIViewController {
return SearchViewController(keyword: keyword)
}
public func fetchProduct(by id: String) async throws -> ProductInfo {
// 先检查缓存
let cacheKey = "product_(id)"
if let cached: ProductInfo = cacheService.get(forKey: cacheKey) {
return cached
}
// 网络请求
let product: ProductInfo = try await networkService.request(
endpoint: .productDetail(id: id)
)
// 缓存
cacheService.set(product, forKey: cacheKey)
return product
}
public func fetchProducts(ids: [String]) async throws -> [ProductInfo] {
return try await networkService.request(
endpoint: .productBatch(ids: ids)
)
}
}
// MARK: - 用户模块实现
public final class UserModuleImpl: NSObject, UserModuleProtocol {
private let loginStateSubject = CurrentValueSubject<Bool, Never>(false)
private var _currentUser: UserInfo?
public override init() {
super.init()
restoreLoginState()
}
// MARK: - ModuleServiceProtocol
public static func moduleDidLoad() {
print("📦 [UserModule] Module loaded")
ModuleManager.shared.register(
protocol: UserModuleProtocol.self,
implementation: UserModuleImpl.self
)
}
// MARK: - UserModuleProtocol
public func loginViewController(completion: ((Bool) -> Void)?) -> UIViewController {
let loginVC = LoginViewController()
loginVC.onComplete = { [weak self] success in
self?.loginStateSubject.send(success)
completion?(success)
}
let nav = UINavigationController(rootViewController: loginVC)
nav.modalPresentationStyle = .fullScreen
return nav
}
public func userCenterViewController() -> UIViewController {
return UserCenterViewController()
}
public var isLoggedIn: Bool {
return _currentUser != nil
}
public var currentUser: UserInfo? {
return _currentUser
}
public var loginStatePublisher: AnyPublisher<Bool, Never> {
return loginStateSubject.eraseToAnyPublisher()
}
public func login(phone: String, code: String) async throws -> UserInfo {
// 登录请求
let user = try await performLogin(phone: phone, code: code)
_currentUser = user
saveLoginState(user: user)
loginStateSubject.send(true)
// 发送登录成功通知
NotificationCenter.default.post(name: .userDidLogin, object: user)
return user
}
public func logout() async throws {
// 登出请求
try await performLogout()
_currentUser = nil
clearLoginState()
loginStateSubject.send(false)
// 发送登出通知
NotificationCenter.default.post(name: .userDidLogout, object: nil)
}
// MARK: - Private
private func performLogin(phone: String, code: String) async throws -> UserInfo {
// 实际登录请求
let networkService = ModuleManager.shared.serviceRequired(for: NetworkServiceProtocol.self)
return try await networkService.request(
endpoint: .login(phone: phone, code: code)
)
}
private func performLogout() async throws {
let networkService = ModuleManager.shared.serviceRequired(for: NetworkServiceProtocol.self)
try await networkService.request(endpoint: .logout)
}
private func restoreLoginState() {
if let data = KeychainHelper.load(forKey: "current_user"),
let user = try? JSONDecoder().decode(UserInfo.self, from: data) {
_currentUser = user
loginStateSubject.send(true)
}
}
private func saveLoginState(user: UserInfo) {
if let data = try? JSONEncoder().encode(user) {
KeychainHelper.save(data, forKey: "current_user")
}
}
private func clearLoginState() {
KeychainHelper.delete(forKey: "current_user")
}
}
// 通知名称扩展
extension Notification.Name {
static let userDidLogin = Notification.Name("userDidLogin")
static let userDidLogout = Notification.Name("userDidLogout")
}
// MARK: - 购物车模块实现
public final class CartModuleImpl: NSObject, CartModuleProtocol {
private var items: [CartItem] = []
private let itemCountSubject = CurrentValueSubject<Int, Never>(0)
private var cancellables = Set<AnyCancellable>()
public override init() {
super.init()
setupObservers()
loadLocalCart()
}
public static func moduleDidLoad() {
print("📦 [CartModule] Module loaded")
ModuleManager.shared.register(
protocol: CartModuleProtocol.self,
implementation: CartModuleImpl.self
)
}
// MARK: - CartModuleProtocol
public func cartViewController() -> UIViewController {
return CartViewController()
}
public func addItem(productId: String, quantity: Int) async throws {
let networkService = ModuleManager.shared.serviceRequired(for: NetworkServiceProtocol.self)
try await networkService.request(
endpoint: .addToCart(productId: productId, quantity: quantity)
)
// 刷新购物车
_ = try await fetchItems()
}
public func removeItem(itemId: String) async throws {
let networkService = ModuleManager.shared.serviceRequired(for: NetworkServiceProtocol.self)
try await networkService.request(endpoint: .removeFromCart(itemId: itemId))
// 刷新购物车
_ = try await fetchItems()
}
public func fetchItems() async throws -> [CartItem] {
let networkService = ModuleManager.shared.serviceRequired(for: NetworkServiceProtocol.self)
items = try await networkService.request(endpoint: .cartList)
let count = items.reduce(0) { $0 + $1.quantity }
itemCountSubject.send(count)
return items
}
public var itemCount: Int {
return itemCountSubject.value
}
public var itemCountPublisher: AnyPublisher<Int, Never> {
return itemCountSubject.eraseToAnyPublisher()
}
// MARK: - Private
private func setupObservers() {
// 监听登录状态,登出时清空购物车
NotificationCenter.default.publisher(for: .userDidLogout)
.sink { [weak self] _ in
self?.items = []
self?.itemCountSubject.send(0)
}
.store(in: &cancellables)
// 登录后刷新购物车
NotificationCenter.default.publisher(for: .userDidLogin)
.sink { [weak self] _ in
Task {
try? await self?.fetchItems()
}
}
.store(in: &cancellables)
}
private func loadLocalCart() {
// 从本地加载购物车缓存
let count = UserDefaults.standard.integer(forKey: "cart_item_count")
itemCountSubject.send(count)
}
}
Protocol-Class 使用示例
// MARK: - 模块初始化(AppDelegate 中)
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// 1. 注册基础服务
registerCoreServices()
// 2. 注册所有业务模块
registerBusinessModules()
// 3. 初始化所有模块
ModuleManager.shared.initializeAllModules()
return true
}
private func registerCoreServices() {
let manager = ModuleManager.shared
// 网络服务
manager.register(protocol: NetworkServiceProtocol.self,
instance: NetworkServiceImpl())
// 缓存服务
manager.register(protocol: CacheServiceProtocol.self,
instance: CacheServiceImpl())
// 数据库服务
manager.register(protocol: DatabaseServiceProtocol.self,
instance: DatabaseServiceImpl())
}
private func registerBusinessModules() {
ModuleManager.shared.registerModules([
UserModuleImpl.self, // 用户模块优先
ProductModuleImpl.self,
CartModuleImpl.self,
OrderModuleImpl.self,
])
}
}
// MARK: - 业务代码中使用
class HomeViewController: UIViewController {
// 方式1:属性包装器注入
@ModuleService private var productModule: ProductModuleProtocol
@ModuleService private var cartModule: CartModuleProtocol
@ModuleService private var userModule: UserModuleProtocol
private var cancellables = Set<AnyCancellable>()
override func viewDidLoad() {
super.viewDidLoad()
setupBindings()
loadData()
}
private func setupBindings() {
// 监听购物车数量变化
cartModule.itemCountPublisher
.receive(on: DispatchQueue.main)
.sink { [weak self] count in
self?.updateCartBadge(count)
}
.store(in: &cancellables)
// 监听登录状态
userModule.loginStatePublisher
.receive(on: DispatchQueue.main)
.sink { [weak self] isLoggedIn in
self?.updateLoginStatus(isLoggedIn)
}
.store(in: &cancellables)
}
private func loadData() {
Task {
do {
let products = try await productModule.fetchProducts(ids: ["1", "2", "3"])
updateUI(with: products)
} catch {
showError(error)
}
}
}
// 跳转商品详情
func showProductDetail(productId: String) {
let vc = productModule.productDetailViewController(productId: productId)
navigationController?.pushViewController(vc, animated: true)
}
// 添加到购物车
func addToCart(productId: String) {
// 检查登录
guard userModule.isLoggedIn else {
let loginVC = userModule.loginViewController { [weak self] success in
if success {
self?.addToCart(productId: productId)
}
}
present(loginVC, animated: true)
return
}
Task {
do {
try await cartModule.addItem(productId: productId, quantity: 1)
showToast("添加成功")
} catch {
showError(error)
}
}
}
// 查看购物车
func showCart() {
let vc = cartModule.cartViewController()
navigationController?.pushViewController(vc, animated: true)
}
private func updateCartBadge(_ count: Int) {
tabBarItem.badgeValue = count > 0 ? "(count)" : nil
}
private func updateLoginStatus(_ isLoggedIn: Bool) {
// 更新UI
}
private func updateUI(with products: [ProductInfo]) {
// 更新商品列表
}
private func showError(_ error: Error) {
// 显示错误
}
private func showToast(_ message: String) {
// 显示提示
}
}
// MARK: - 方式2:直接通过 ModuleManager 获取
class SomeOtherViewController: UIViewController {
func doSomething() {
// 获取服务
guard let productModule = ModuleManager.shared.service(for: ProductModuleProtocol.self) else {
print("商品模块未注册")
return
}
// 使用服务
let vc = productModule.productDetailViewController(productId: "123")
navigationController?.pushViewController(vc, animated: true)
}
}
混合路由架构
统一路由门面
// MARK: - 统一路由门面(混合 URLRouter + Mediator + Protocol)
/// 统一路由器 - 整合多种路由方案
public final class UnifiedRouter {
public static let shared = UnifiedRouter()
private let urlRouter = URLRouter.shared
private let mediator = Mediator.shared
private let moduleManager = ModuleManager.shared
private init() {}
// ═══════════════════════════════════════════════════════════════
// MARK: - URL 路由(页面跳转 + DeepLink)
// ═══════════════════════════════════════════════════════════════
/// 通过 URL 导航
public func navigate(
to urlString: String,
from sourceVC: UIViewController? = nil,
options: RouteOptions = .default,
extra: [String: Any]? = nil
) {
urlRouter.open(urlString, from: sourceVC, options: options, extra: extra)
}
/// 通过 URL 对象导航
public func navigate(
to url: URL,
from sourceVC: UIViewController? = nil,
options: RouteOptions = .default
) {
urlRouter.open(url: url, from: sourceVC, options: options)
}
/// 检查 URL 是否可以处理
public func canHandle(_ urlString: String) -> Bool {
return urlRouter.canOpen(urlString)
}
// ═══════════════════════════════════════════════════════════════
// MARK: - Mediator 调用(组件间服务调用)
// ═══════════════════════════════════════════════════════════════
/// Target-Action 调用
@discardableResult
public func perform(
target: String,
action: String,
params: [String: Any]? = nil
) -> Any? {
return mediator.performTarget(target, action: action, params: params)
}
// ═══════════════════════════════════════════════════════════════
// MARK: - Protocol 服务获取
// ═══════════════════════════════════════════════════════════════
/// 获取模块服务
public func service<T>(for protocol: T.Type) -> T? {
return moduleManager.service(for: `protocol`)
}
/// 获取必需的模块服务
public func serviceRequired<T>(for protocol: T.Type) -> T {
return moduleManager.serviceRequired(for: `protocol`)
}
// ═══════════════════════════════════════════════════════════════
// MARK: - 便捷方法
// ═══════════════════════════════════════════════════════════════
/// 返回上一页
public func goBack(from viewController: UIViewController, animated: Bool = true) {
if let nav = viewController.navigationController, nav.viewControllers.count > 1 {
nav.popViewController(animated: animated)
} else {
viewController.dismiss(animated: animated)
}
}
/// 返回根页面
public func popToRoot(from viewController: UIViewController, animated: Bool = true) {
viewController.navigationController?.popToRootViewController(animated: animated)
}
/// 关闭所有模态页面
public func dismissAll(from viewController: UIViewController,
animated: Bool = true,
completion: (() -> Void)? = nil) {
var current = viewController
while let presenting = current.presentingViewController {
current = presenting
}
current.dismiss(animated: animated, completion: completion)
}
}
// MARK: - 统一路由扩展 - 商品模块
public extension UnifiedRouter {
// URL方式
func openProductDetail(productId: String, from vc: UIViewController? = nil) {
navigate(to: "/product/(productId)", from: vc)
}
func openProductList(category: String? = nil, from vc: UIViewController? = nil) {
var url = "/products"
if let category = category {
url += "?category=(category)"
}
navigate(to: url, from: vc)
}
// Mediator方式(获取VC后自定义处理)
func productDetailVC(productId: String) -> UIViewController {
return mediator.productDetailViewController(productId: productId)
}
// Protocol方式(获取服务能力)
var productService: ProductModuleProtocol {
return serviceRequired(for: ProductModuleProtocol.self)
}
}
// MARK: - 统一路由扩展 - 购物车模块
public extension UnifiedRouter {
func openCart(from vc: UIViewController? = nil) {
navigate(to: "/cart", from: vc)
}
@discardableResult
func addToCart(productId: String, quantity: Int = 1) async throws -> Bool {
let cartService = serviceRequired(for: CartModuleProtocol.self)
try await cartService.addItem(productId: productId, quantity: quantity)
return true
}
var cartItemCount: Int {
return service(for: CartModuleProtocol.self)?.itemCount ?? 0
}
}
// MARK: - 统一路由扩展 - 用户模块
public extension UnifiedRouter {
var isLoggedIn: Bool {
return service(for: UserModuleProtocol.self)?.isLoggedIn ?? false
}
var currentUser: UserInfo? {
return service(for: UserModuleProtocol.self)?.currentUser
}
/// 需要登录时调用
func requireLogin(
from vc: UIViewController,
completion: @escaping (Bool) -> Void
) {
guard !isLoggedIn else {
completion(true)
return
}
guard let userService = service(for: UserModuleProtocol.self) else {
completion(false)
return
}
let loginVC = userService.loginViewController(completion: completion)
vc.present(loginVC, animated: true)
}
func logout() async throws {
try await serviceRequired(for: UserModuleProtocol.self).logout()
}
}
路由配置表(动态化支持)
// MARK: - 路由配置(支持动态下发)
/// 路由配置模型
public struct RouteConfig: Codable {
/// 路由路径
let path: String
/// 处理方式
let handlerType: HandlerType
/// 目标信息
let target: TargetInfo?
/// 是否需要登录
let requireLogin: Bool
/// 是否降级到H5
let degradeToH5: Bool
/// H5降级地址
let h5URL: String?
/// 最低支持版本
let minVersion: String?
/// 是否启用
let enabled: Bool
enum HandlerType: String, Codable {
case native // 原生页面
case webView // H5页面
case action // 执行动作
case redirect // 重定向
}
struct TargetInfo: Codable {
let targetName: String
let actionName: String
let defaultParams: [String: String]?
}
}
/// 路由配置管理器
public final class RouteConfigManager {
public static let shared = RouteConfigManager()
/// 本地配置
private var localConfigs: [String: RouteConfig] = [:]
/// 远程配置
private var remoteConfigs: [String: RouteConfig] = [:]
/// 当前App版本
private let appVersion: String
private init() {
appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0.0"
loadLocalConfigs()
}
/// 加载本地配置
private func loadLocalConfigs() {
guard let url = Bundle.main.url(forResource: "route_config", withExtension: "json"),
let data = try? Data(contentsOf: url),
let configs = try? JSONDecoder().decode([RouteConfig].self, from: data) else {
return
}
for config in configs {
localConfigs[config.path] = config
}
}
/// 更新远程配置
public func updateRemoteConfigs(_ configs: [RouteConfig]) {
for config in configs {
remoteConfigs[config.path] = config
}
}
/// 获取路由配置
public func config(for path: String) -> RouteConfig? {
// 远程配置优先
if let remote = remoteConfigs[path] {
return validateConfig(remote) ? remote : nil
}
if let local = localConfigs[path] {
return validateConfig(local) ? local : nil
}
return nil
}
/// 验证配置有效性
private func validateConfig(_ config: RouteConfig) -> Bool {
// 检查是否启用
guard config.enabled else { return false }
// 检查版本要求
if let minVersion = config.minVersion {
if appVersion.compare(minVersion, options: .numeric) == .orderedAscending {
return false
}
}
return true
}
}
// MARK: - 配置驱动的路由拦截器
public final class ConfigDrivenInterceptor: RouteInterceptor {
public var priority: Int { return 200 }
public func shouldIntercept(url: URL, params: RouteParams) -> Bool {
// 检查是否有对应配置
return RouteConfigManager.shared.config(for: url.path) != nil
}
public func intercept(
url: URL,
params: RouteParams,
completion: @escaping (InterceptResult) -> Void
) {
guard let config = RouteConfigManager.shared.config(for: url.path) else {
completion(.continue)
return
}
// 检查登录要求
if config.requireLogin && !UnifiedRouter.shared.isLoggedIn {
let loginURL = URL(string: "myapp:///login?redirect=(url.absoluteString.urlEncoded)")!
completion(.redirect(loginURL))
return
}
// 检查是否降级
if config.degradeToH5, let h5URL = config.h5URL {
let webViewURL = URL(string: "myapp:///webview?url=(h5URL.urlEncoded)")!
completion(.redirect(webViewURL))
return
}
// 处理重定向
if config.handlerType == .redirect, let target = config.target {
let redirectURL = URL(string: "myapp:///(target.targetName)/(target.actionName)")!
completion(.redirect(redirectURL))
return
}
completion(.continue)
}
}
使用示例
// MARK: - 最终使用示例
class ProductViewController: UIViewController {
// 统一使用 UnifiedRouter
private let router = UnifiedRouter.shared
// MARK: - 页面跳转(URL方式)
func showProductDetail(productId: String) {
// 最简单的方式
router.navigate(to: "/product/(productId)")
}
func showCart() {
// 使用便捷方法
router.openCart(from: self)
}
func showOrders() {
// 指定导航方式
router.navigate(
to: "/orders",
from: self,
options: RouteOptions(navigationType: .push)
)
}
// MARK: - 服务调用(Protocol方式)
func loadProducts() async {
do {
let products = try await router.productService.fetchProducts(ids: ["1", "2", "3"])
updateUI(products)
} catch {
showError(error)
}
}
func addToCart(productId: String) async {
// 检查登录
router.requireLogin(from: self) { [weak self] success in
guard success else { return }
Task {
do {
try await self?.router.addToCart(productId: productId)
self?.showToast("添加成功")
} catch {
self?.showError(error)
}
}
}
}
// MARK: - 获取ViewController自定义处理(Mediator方式)
func showProductWithCustomAnimation(productId: String) {
let vc = router.productDetailVC(productId: productId)
// 自定义转场
let transition = CATransition()
transition.type = .fade
transition.duration = 0.3
navigationController?.view.layer.add(transition, forKey: nil)
navigationController?.pushViewController(vc, animated: false)
}
// MARK: - 私有方法
private func updateUI(_ products: [ProductInfo]) {}
private func showError(_ error: Error) {}
private func showToast(_ message: String) {}
}
高级特性实现
路由动画配置
// MARK: - 路由转场动画
/// 转场动画类型
public enum RouteTransition {
case none
case push
case present
case fade
case slideFromRight
case slideFromBottom
case zoom
case custom(UIViewControllerAnimatedTransitioning)
}
/// 转场动画管理器
public final class RouteTransitionManager: NSObject {
public static let shared = RouteTransitionManager()
private var transitions: [String: RouteTransition] = [:]
/// 注册路由转场动画
public func register(path: String, transition: RouteTransition) {
transitions[path] = transition
}
/// 获取路由对应的转场动画
public func transition(for path: String) -> RouteTransition {
return transitions[path] ?? .push
}
}
/// 自定义导航控制器(支持路由动画)
public class RouteNavigationController: UINavigationController {
private var currentTransition: RouteTransition = .push
public func pushViewController(
_ viewController: UIViewController,
transition: RouteTransition,
animated: Bool
) {
self.currentTransition = transition
switch transition {
case .none:
pushViewController(viewController, animated: false)
case .push:
pushViewController(viewController, animated: animated)
case .fade:
let fadeTransition = CATransition()
fadeTransition.type = .fade
fadeTransition.duration = 0.3
view.layer.add(fadeTransition, forKey: nil)
pushViewController(viewController, animated: false)
case .slideFromRight:
let slideTransition = CATransition()
slideTransition.type = .push
slideTransition.subtype = .fromRight
slideTransition.duration = 0.3
view.layer.add(slideTransition, forKey: nil)
pushViewController(viewController, animated: false)
case .slideFromBottom:
let slideTransition = CATransition()
slideTransition.type = .push
slideTransition.subtype = .fromTop
slideTransition.duration = 0.3
view.layer.add(slideTransition, forKey: nil)
pushViewController(viewController, animated: false)
case .zoom:
delegate = self
pushViewController(viewController, animated: animated)
case .present:
let nav = UINavigationController(rootViewController: viewController)
present(nav, animated: animated)
case .custom(let animator):
delegate = self
pushViewController(viewController, animated: animated)
}
}
}
extension RouteNavigationController: UINavigationControllerDelegate {
public func navigationController(
_ navigationController: UINavigationController,
animationControllerFor operation: UINavigationController.Operation,
from fromVC: UIViewController,
to toVC: UIViewController
) -> UIViewControllerAnimatedTransitioning? {
switch currentTransition {
case .zoom:
return ZoomTransitionAnimator(operation: operation)
case .custom(let animator):
return animator
default:
return nil
}
}
}
/// 缩放转场动画
class ZoomTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {
let operation: UINavigationController.Operation
init(operation: UINavigationController.Operation) {
self.operation = operation
super.init()
}
func transitionDuration(
using transitionContext: UIViewControllerContextTransitioning?
) -> TimeInterval {
return 0.3
}
func animateTransition(
using transitionContext: UIViewControllerContextTransitioning
) {
let containerView = transitionContext.containerView
guard let fromView = transitionContext.view(forKey: .from),
let toView = transitionContext.view(forKey: .to) else {
transitionContext.completeTransition(false)
return
}
let duration = transitionDuration(using: transitionContext)
if operation == .push {
// Push: 新页面从小变大
toView.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
toView.alpha = 0
containerView.addSubview(toView)
UIView.animate(withDuration: duration, animations: {
fromView.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
fromView.alpha = 0
toView.transform = .identity
toView.alpha = 1
}) { _ in
fromView.transform = .identity
fromView.alpha = 1
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
} else {
// Pop: 当前页面变小消失
containerView.insertSubview(toView, belowSubview: fromView)
toView.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
toView.alpha = 0
UIView.animate(withDuration: duration, animations: {
fromView.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
fromView.alpha = 0
toView.transform = .identity
toView.alpha = 1
}) { _ in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
}
}
路由栈管理
// MARK: - 路由栈管理
/// 路由栈管理器
public final class RouteStackManager {
public static let shared = RouteStackManager()
/// 路由历史记录
private var routeHistory: [RouteRecord] = []
/// 最大历史记录数
private let maxHistoryCount = 50
private init() {}
/// 路由记录
public struct RouteRecord {
let url: URL
let timestamp: Date
let params: RouteParams
let source: String?
}
/// 记录路由
public func record(url: URL, params: RouteParams, source: String? = nil) {
let record = RouteRecord(
url: url,
timestamp: Date(),
params: params,
source: source
)
routeHistory.append(record)
// 限制历史记录数量
if routeHistory.count > maxHistoryCount {
routeHistory.removeFirst()
}
}
/// 获取上一个路由
public var previousRoute: RouteRecord? {
guard routeHistory.count >= 2 else { return nil }
return routeHistory[routeHistory.count - 2]
}
/// 获取路由历史
public var history: [RouteRecord] {
return routeHistory
}
/// 清空历史
public func clearHistory() {
routeHistory.removeAll()
}
/// 返回到指定路由
public func popTo(path: String, from viewController: UIViewController) -> Bool {
guard let nav = viewController.navigationController else { return false }
// 在导航栈中查找目标
for vc in nav.viewControllers.reversed() {
if let routePath = vc.routePath, routePath == path {
nav.popToViewController(vc, animated: true)
return true
}
}
return false
}
/// 移除指定路由之后的所有页面
public func removeAfter(path: String, in viewController: UIViewController) {
guard let nav = viewController.navigationController else { return }
var targetIndex: Int?
for (index, vc) in nav.viewControllers.enumerated() {
if let routePath = vc.routePath, routePath == path {
targetIndex = index
break
}
}
if let index = targetIndex, index < nav.viewControllers.count - 1 {
nav.viewControllers = Array(nav.viewControllers.prefix(index + 1))
}
}
}
// 给 UIViewController 添加路由路径属性
private var routePathKey: UInt8 = 0
extension UIViewController {
/// 当前页面对应的路由路径
public var routePath: String? {
get {
return objc_getAssociatedObject(self, &routePathKey) as? String
}
set {
objc_setAssociatedObject(self, &routePathKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
路由调试工具
// MARK: - 路由调试工具
#if DEBUG
/// 路由调试面板
public final class RouteDebugger {
public static let shared = RouteDebugger()
/// 是否启用调试
public var isEnabled = true
/// 路由日志
private var logs: [RouteLog] = []
private init() {}
struct RouteLog {
let timestamp: Date
let url: String
let params: [String: Any]
let result: String
let duration: TimeInterval
}
/// 记录路由日志
public func log(
url: String,
params: [String: Any],
result: String,
duration: TimeInterval
) {
guard isEnabled else { return }
let log = RouteLog(
timestamp: Date(),
url: url,
params: params,
result: result,
duration: duration
)
logs.append(log)
print("""
════════════════════════════════════════
🛣️ [Route Debug]
URL: (url)
Params: (params)
Result: (result)
Duration: (String(format: "%.3f", duration))s
════════════════════════════════════════
""")
}
/// 显示调试面板
public func showDebugPanel(from viewController: UIViewController) {
let debugVC = RouteDebugViewController(logs: logs)
let nav = UINavigationController(rootViewController: debugVC)
viewController.present(nav, animated: true)
}
/// 导出日志
public func exportLogs() -> String {
var output = "Route Debug Logs
"
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
for log in logs {
output += """
[(dateFormatter.string(from: log.timestamp))]
URL: (log.url)
Params: (log.params)
Result: (log.result)
Duration: (log.duration)s
"""
}
return output
}
/// 清空日志
public func clearLogs() {
logs.removeAll()
}
}
/// 路由调试ViewController
class RouteDebugViewController: UITableViewController {
private let logs: [RouteDebugger.RouteLog]
init(logs: [RouteDebugger.RouteLog]) {
self.logs = logs.reversed()
super.init(style: .plain)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
title = "Route Debug"
navigationItem.rightBarButtonItem = UIBarButtonItem(
title: "Close",
style: .done,
target: self,
action: #selector(closeTapped)
)
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
@objc private func closeTapped() {
dismiss(animated: true)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return logs.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let log = logs[indexPath.row]
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH:mm:ss"
cell.textLabel?.numberOfLines = 0
cell.textLabel?.font = .systemFont(ofSize: 12)
cell.textLabel?.text = """
[(dateFormatter.string(from: log.timestamp))] (log.result)
(log.url)
"""
return cell
}
}
#endif
性能优化与最佳实践
性能优化
// MARK: - 路由性能优化
/// 路由缓存管理
public final class RouteCacheManager {
public static let shared = RouteCacheManager()
/// ViewController 缓存
private var viewControllerCache: NSCache<NSString, UIViewController>
/// 路由匹配结果缓存
private var matchResultCache: NSCache<NSString, RouteMatchResult>
private init() {
viewControllerCache = NSCache()
viewControllerCache.countLimit = 10 // 最多缓存10个VC
matchResultCache = NSCache()
matchResultCache.countLimit = 100 // 最多缓存100个匹配结果
}
/// 缓存的路由匹配结果
class RouteMatchResult: NSObject {
let pattern: String
let handler: Any
init(pattern: String, handler: Any) {
self.pattern = pattern
self.handler = handler
}
}
/// 获取缓存的ViewController
public func cachedViewController(for key: String) -> UIViewController? {
return viewControllerCache.object(forKey: key as NSString)
}
/// 缓存ViewController
public func cacheViewController(_ vc: UIViewController, for key: String) {
viewControllerCache.setObject(vc, forKey: key as NSString)
}
/// 获取缓存的匹配结果
public func cachedMatchResult(for path: String) -> RouteMatchResult? {
return matchResultCache.object(forKey: path as NSString)
}
/// 缓存匹配结果
public func cacheMatchResult(_ result: RouteMatchResult, for path: String) {
matchResultCache.setObject(result, forKey: path as NSString)
}
/// 清理缓存
public func clearCache() {
viewControllerCache.removeAllObjects()
matchResultCache.removeAllObjects()
}
/// 预热常用路由
public func preloadCommonRoutes(_ paths: [String]) {
for path in paths {
// 预先匹配路由,缓存结果
_ = URLRouter.shared.canOpen(path)
}
}
}
// MARK: - 路由预加载
public extension URLRouter {
/// 预加载页面
func preload(_ urlString: String, extra: [String: Any]? = nil) {
guard let vc = viewController(for: urlString, extra: extra) else { return }
// 触发 viewDidLoad
_ = vc.view
// 缓存
RouteCacheManager.shared.cacheViewController(vc, for: urlString)
}
/// 批量预加载
func preloadBatch(_ urlStrings: [String]) {
DispatchQueue.global(qos: .utility).async {
for urlString in urlStrings {
self.preload(urlString)
}
}
}
}
最佳实践总结
/*
════════════════════════════════════════════════════════════════════
路由最佳实践总结
════════════════════════════════════════════════════════════════════
✅ 架构选择
─────────────────────────────────────────────────────────────────────
• 页面跳转 + DeepLink → URL Router
• 组件服务调用 → Protocol-Class
• 临时兼容/快速开发 → Target-Action
• 复杂场景 → 混合方案
✅ URL设计规范
─────────────────────────────────────────────────────────────────────
• 使用统一的 Scheme:myapp://
• 路径使用小写 + 中划线:/product-detail
• 参数命名使用驼峰:productId, pageSize
• 保持路径简洁:最多3级 /a/b/c
✅ 安全性
─────────────────────────────────────────────────────────────────────
• 敏感操作必须登录拦截
• URL参数需要验证和过滤
• 防止 URL 注入攻击
• 外部 URL 来源校验
✅ 可维护性
─────────────────────────────────────────────────────────────────────
• 路由路径定义常量化
• 提供类型安全的扩展方法
• 完善的日志和调试工具
• 路由文档自动生成
✅ 性能优化
─────────────────────────────────────────────────────────────────────
• 路由匹配结果缓存
• 高频页面预加载
• 按需注册,延迟加载
• 避免在路由处理中做耗时操作
✅ 测试策略
─────────────────────────────────────────────────────────────────────
• 路由注册测试
• 路由匹配测试
• 拦截器测试
• DeepLink 集成测试
*/
// MARK: - 路由常量定义(推荐)
public enum RoutePaths {
// 首页
public static let home = "/home"
// 商品
public enum Product {
public static let detail = "/product/:id"
public static let list = "/products"
public static let search = "/search"
public static func detailPath(id: String) -> String {
return "/product/(id)"
}
}
// 购物车
public enum Cart {
public static let main = "/cart"
public static let checkout = "/checkout"
}
// 订单
public enum Order {
public static let list = "/orders"
public static let detail = "/order/:id"
public static func detailPath(id: String) -> String {
return "/order/(id)"
}
}
// 用户
public enum User {
public static let login = "/login"
public static let register = "/register"
public static let center = "/user"
public static let settings = "/user/settings"
}
// 通用
public enum Common {
public static let webView = "/webview"
public static let share = "/share"
}
}
// MARK: - 使用常量
class SomeVC: UIViewController {
func example() {
// ✅ 使用常量
UnifiedRouter.shared.navigate(to: RoutePaths.Product.detailPath(id: "123"))
// ❌ 避免硬编码
// UnifiedRouter.shared.navigate(to: "/product/123")
}
}
总结
┌─────────────────────────────────────────────────────────────────────┐
│ iOS 路由机制核心要点 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 🔗 URL Scheme │
│ • 应用间跳转 & DeepLink 基础 │
│ • Universal Links 更安全 │
│ • 配置简单,接入成本低 │
│ │
│ 🛣️ URL Router │
│ • 适合页面跳转场景 │
│ • 支持 DeepLink、Push 通知 │
│ • 动态化能力强 │
│ • 缺点:参数传递受限 │
│ │
│ 🎯 Target-Action │
│ • 无需注册,运行时调用 │
│ • 适合组件能力暴露 │
│ • 支持复杂参数传递 │
│ • 缺点:硬编码,无编译检查 │
│ │
│ 📋 Protocol-Class │
│ • 类型安全,编译期检查 │
│ • 适合服务层调用 │
│ • IDE 支持好 │
│ • 缺点:协议维护成本 │
│ │
│ 🔀 混合方案(推荐) │
│ • URL Router: 页面跳转 + DeepLink │
│ • Protocol: 服务调用 + 数据获取 │
│ • Target-Action: 兼容 & 补充 │
│ │
└─────────────────────────────────────────────────────────────────────┘
路由机制是组件化架构的核心基础设施,选择合适的路由方案并做好设计,能大大提升项目的可维护性和开发效率。
参考资源:
JLRoutesCTMediatorMGJRouterBeeHiveApple – Defining a Custom URL SchemeApple – Universal Links
© 版权声明
文章版权归作者所有,未经允许请勿转载。如内容涉嫌侵权,请在本页底部进入<联系我们>进行举报投诉!
THE END

















暂无评论内容