JDRouter 完整实现及子模块调用流程
1. JDRouter 核心实现
- JDRouter.h
#import <Foundation/Foundation.h>
// 模块接口声明宏
#define JDROUTER_EXTERN_METHOD(m, i, p, c) \
+ (id) routerHandle_##m##_##i:(NSDictionary*)arg callback:(void(^)(id result))callback
typedef void (^JDRouterHandler)(NSDictionary *params, void(^completion)(id result));
typedef BOOL (^JDInterceptorBlock)(NSString *url, NSDictionary *params);
@interface JDRouter : NSObject
// 单例
+ (instancetype)sharedRouter;
// 基础路由注册
- (void)registerURL:(NSString *)URL handler:(JDRouterHandler)handler;
// URL 跳转
- (BOOL)openURL:(NSString *)URL;
- (BOOL)openURL:(NSString *)URL params:(NSDictionary *)params;
- (BOOL)openURL:(NSString *)URL params:(NSDictionary *)params completion:(void(^)(id result))completion;
// 拦截器管理
- (void)addInterceptor:(JDInterceptorBlock)interceptor;
- (void)removeInterceptor:(JDInterceptorBlock)interceptor;
// 自动注册接口
+ (void)enableAutoRegister;
@end
- JDRouter.m
#import "JDRouter.h"
#import <objc/runtime.h>
@interface JDRouter ()
@property (nonatomic, strong) NSMutableDictionary<NSString *, JDRouterHandler> *routeMap;
@property (nonatomic, strong) NSMutableArray<JDInterceptorBlock> *interceptors;
@property (nonatomic, strong) dispatch_semaphore_t lock;
@property (nonatomic, assign) BOOL autoRegisterEnabled;
@end
@implementation JDRouter
+ (instancetype)sharedRouter {
static JDRouter *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[JDRouter alloc] init];
});
return instance;
}
- (instancetype)init {
if (self = [super init]) {
_routeMap = [NSMutableDictionary dictionary];
_interceptors = [NSMutableArray array];
_lock = dispatch_semaphore_create(1);
}
return self;
}
#pragma mark - 路由注册
- (void)registerURL:(NSString *)URL handler:(JDRouterHandler)handler {
if (!URL || !handler) return;
dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER);
NSString *normalizedURL = [[URL stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] lowercaseString];
self.routeMap[normalizedURL] = [handler copy];
dispatch_semaphore_signal(_lock);
}
#pragma mark - URL 跳转
- (BOOL)openURL:(NSString *)URL {
return [self openURL:URL params:nil completion:nil];
}
- (BOOL)openURL:(NSString *)URL params:(NSDictionary *)params {
return [self openURL:URL params:params completion:nil];
}
- (BOOL)openURL:(NSString *)URL params:(NSDictionary *)params completion:(void(^)(id))completion {
// 1. 标准化URL
NSString *normalizedURL = [[URL stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] lowercaseString];
// 2. 执行拦截器链
for (JDInterceptorBlock interceptor in self.interceptors) {
if (!interceptor(normalizedURL, params)) {
NSLog(@"[JDRouter] 跳转被拦截: %@", URL);
return NO;
}
}
// 3. 匹配Handler
JDRouterHandler handler = nil;
dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER);
handler = self.routeMap[normalizedURL];
dispatch_semaphore_signal(_lock);
if (!handler) {
// 降级处理
[self degradeToWebView:normalizedURL params:params];
return NO;
}
// 4. 参数合并与执行
NSDictionary *queryParams = [self parseQueryParameters:normalizedURL];
NSMutableDictionary *finalParams = [NSMutableDictionary dictionaryWithDictionary:params];
[finalParams addEntriesFromDictionary:queryParams];
// 5. 执行路由处理器
handler(finalParams, completion);
return YES;
}
#pragma mark - 拦截器管理
- (void)addInterceptor:(JDInterceptorBlock)interceptor {
if (interceptor) {
dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER);
[self.interceptors addObject:[interceptor copy]];
dispatch_semaphore_signal(_lock);
}
}
- (void)removeInterceptor:(JDInterceptorBlock)interceptor {
dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER);
[self.interceptors removeObject:interceptor];
dispatch_semaphore_signal(_lock);
}
#pragma mark - 自动注册
+ (void)enableAutoRegister {
[JDRouter sharedRouter].autoRegisterEnabled = YES;
[self performAutoRegistration];
}
+ (void)performAutoRegistration {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self scanAndRegisterAllModules];
});
}
+ (void)scanAndRegisterAllModules {
int classCount = objc_getClassList(NULL, 0);
Class *classes = (Class *)malloc(sizeof(Class) * classCount);
classCount = objc_getClassList(classes, classCount);
for (int i = 0; i < classCount; i++) {
Class cls = classes[i];
// 跳过系统类
if ([NSStringFromClass(cls) hasPrefix:@"NS"] ||
[NSStringFromClass(cls) hasPrefix:@"UI"]) {
continue;
}
unsigned int methodCount = 0;
Method *methods = class_copyMethodList(object_getClass(cls), &methodCount);
for (unsigned int j = 0; j < methodCount; j++) {
SEL selector = method_getName(methods[j]);
NSString *methodName = NSStringFromSelector(selector);
if ([methodName hasPrefix:@"routerHandle_"]) {
NSArray *components = [methodName componentsSeparatedByString:@"_"];
if (components.count >= 3) {
NSString *module = components[1];
NSString *interface = components[2];
NSString *URL = [NSString stringWithFormat:@"jd://%@/%@", module, interface];
[self registerDynamicRouteForClass:cls selector:selector URL:URL];
}
}
}
free(methods);
}
free(classes);
}
+ (void)registerDynamicRouteForClass:(Class)cls selector:(SEL)selector URL:(NSString *)URL {
[[JDRouter sharedRouter] registerURL:URL handler:^(NSDictionary *params, void (^completion)(id)) {
NSMethodSignature *signature = [cls methodSignatureForSelector:selector];
if (signature.numberOfArguments != 4) {
NSLog(@"[JDRouter] 方法参数数量不匹配: %@", NSStringFromSelector(selector));
return;
}
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
invocation.target = cls;
invocation.selector = selector;
// 设置参数
[invocation setArgument:¶ms atIndex:2];
[invocation setArgument:&completion atIndex:3];
[invocation invoke];
if (signature.methodReturnLength > 0) {
__unsafe_unretained id returnValue;
[invocation getReturnValue:&returnValue];
if (completion) completion(returnValue);
}
}];
}
#pragma mark - 辅助方法
- (NSDictionary *)parseQueryParameters:(NSString *)URLString {
NSURL *url = [NSURL URLWithString:URLString];
if (!url) return @{};
NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
for (NSURLQueryItem *item in components.queryItems) {
if (item.name && item.value) {
params[item.name] = item.value;
}
}
return [params copy];
}
- (void)degradeToWebView:(NSString *)URL params:(NSDictionary *)params {
NSLog(@"[JDRouter] 降级到WebView: %@", URL);
// 实际项目中会打开H5页面
}
@end
2. 子模块实现(商品模块)
- JDProductModule.h
#import <Foundation/Foundation.h>
@interface JDProductModule : NSObject
// .h文件是空的
@end
- JDProductModule.m
#import "JDProductModule.h"
#import "JDProductDetailViewController.h"
#import "JDRouter.h"
@implementation JDProductModule
+ (void)load {
// 确保自动注册已启用
[JDRouter enableAutoRegister];
}
// 商品详情页实现
JDROUTER_EXTERN_METHOD(JDProductModule, detail, arg, callback) {
// 1. 参数解析
NSString *productId = arg[@"id"];
if (!productId) {
if (callback) {
callback(@{@"status": @"error", @"message": @"缺少商品ID"});
}
return nil;
}
// 2. 创建视图控制器
JDProductDetailViewController *vc = [[JDProductDetailViewController alloc] init];
vc.productId = productId;
vc.sourceFrom = arg[@"source"] ?: @"unknown";
// 3. 页面跳转
UIViewController *topVC = [self topViewController];
if ([topVC isKindOfClass:[UINavigationController class]]) {
[(UINavigationController *)topVC pushViewController:vc animated:YES];
} else {
[topVC presentViewController:vc animated:YES completion:nil];
}
// 4. 返回结果
NSDictionary *result = @{
@"status": @"success",
@"viewController": vc,
@"timestamp": @(NSDate.date.timeIntervalSince1970)
};
if (callback) callback(result);
return result;
}
// 获取顶层控制器
+ (UIViewController *)topViewController {
UIViewController *rootVC = UIApplication.sharedApplication.keyWindow.rootViewController;
return [self findTopViewController:rootVC];
}
+ (UIViewController *)findTopViewController:(UIViewController *)vc {
if ([vc isKindOfClass:[UINavigationController class]]) {
return [self findTopViewController:[(UINavigationController *)vc topViewController]];
} else if ([vc isKindOfClass:[UITabBarController class]]) {
return [self findTopViewController:[(UITabBarController *)vc selectedViewController]];
} else if (vc.presentedViewController) {
return [self findTopViewController:vc.presentedViewController];
}
return vc;
}
@end
3. 拦截器实现(登录检查)
// AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 添加登录拦截器
[[JDRouter sharedRouter] addInterceptor:^BOOL(NSString *url, NSDictionary *params) {
// 需要登录的页面路径
if ([url containsString:@"jd://product/detail"] ||
[url containsString:@"jd://order/create"]) {
if (![UserManager isLogin]) {
NSLog(@"[JDRouter] 需要登录,跳转到登录页");
// 保存原始请求
NSDictionary *redirectInfo = @{
@"url": url,
@"params": params ?: @{}
};
[[NSUserDefaults standardUserDefaults] setObject:redirectInfo forKey:@"redirectAfterLogin"];
// 跳转到登录页
[[JDRouter sharedRouter] openURL:@"jd://user/login"];
return NO; // 中断原始跳转
}
}
return YES;
}];
return YES;
}
4. 调用商品模块的完整流程
- 其他页面调起商品详情
- (void)didSelectProduct:(NSString *)productId {
NSString *url = [NSString stringWithFormat:@"jd://product/detail?id=%@&source=home", productId];
[[JDRouter sharedRouter] openURL:url params:@{
@"promotion": @"summer_sale",
@"position": @(self.selectedIndex)
} completion:^(id result) {
if ([result[@"status"] isEqualToString:@"success"]) {
NSLog(@"✅ 成功打开商品页: %@", productId);
} else {
NSLog(@"❌ 打开商品页失败: %@", result[@"message"]);
}
}];
}
5. 完整调用时序图
完整调用时序图
6. 关键步骤解析
-
自动注册触发
- App 启动时调用
[JDRouter enableAutoRegister] - 在
+load中扫描所有类方法 - 发现
routerHandle_Product_detail:方法 - 注册路由
jd://product/detail
- App 启动时调用
-
调用过程
调用过程
-
参数传递
- URL 参数:
id=123&source=home - 代码参数:
@{@"promotion": @"summer_sale"} - 合并后参数:
{ "id": "123", "source": "home", "promotion": "summer_sale", "position": 2 }
- URL 参数:
-
跨模块通信
- 通过 completion block 返回结果:
@{ @"status": @"success", @"viewController": vc, @"timestamp": 1689291000 }
- 通过 completion block 返回结果:
7. 高级功能扩展
- 路由调试工具
// JDRouter+Debug.m
@implementation JDRouter (Debug)
- (void)printAllRoutes {
dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER);
NSLog(@"====== 注册的路由 ======");
[self.routeMap enumerateKeysAndObjectsUsingBlock:^(NSString *url, JDRouterHandler handler, BOOL *stop) {
NSLog(@"URL: %@", url);
}];
NSLog(@"======================");
dispatch_semaphore_signal(_lock);
}
- (BOOL)canOpenURL:(NSString *)URL {
NSString *normalizedURL = [[URL stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] lowercaseString];
return self.routeMap[normalizedURL] != nil;
}
@end
- 动态路由更新
// 服务端下发路由配置
- (void)updateRoutesFromServer {
[NetworkManager fetchRouteConfig:^(NSDictionary *config) {
for (NSString *url in config[@"routes"]) {
[self registerURL:url handler:^(NSDictionary *params, void (^completion)(id)) {
// 跳转到H5页面
[self openWebURL:url params:params];
}];
}
}];
}
- AOP 路由监控
// 路由性能监控
[JDRouter sharedRouter].globalCompletion = ^(NSString *URL, NSTimeInterval duration) {
[Analytics logEvent:@"router_performance" params:@{
@"url": URL,
@"duration": @(duration)
}];
};
// 在openURL方法中添加
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
handler(finalParams, ^(id result){
if (completion) completion(result);
CFAbsoluteTime endTime = CFAbsoluteTimeGetCurrent();
if (self.globalCompletion) {
self.globalCompletion(URL, endTime - startTime);
}
});
- 实际应用场景
-
商品详情页跳转
// Swift调用示例 JDRouter.shared().openURL("jd://product/detail?id=10086", params: ["source": "recommend"], completion: { result in if let resultDict = result as? [String: Any], resultDict["status"] as? String == "success" { print("成功打开商品页") } }) -
服务调用
// 调用加入购物车服务 [[JDRouter sharedRouter] openURL:@"jd://cart/add" params:@{ @"product_id": @"P1001", @"quantity": @2, @"sku": @"red_large" }]; -
跨模块回调
// 支付结果回调 [[JDRouter sharedRouter] openURL:@"jd://order/pay" params:@{@"order_id": @"O2023001"} completion:^(NSDictionary *result) { if ([result[@"status"] isEqual:@"success"]) { [self showSuccessMessage:@"支付成功"]; } else { [self showErrorMessage:result[@"reason"]]; } }];
总结
JDRouter 大型 App 实现模块化架构的核心基础模块,主要技术点为:
-
路由注册
- 自动扫描
routerHandle_前缀的类方法 - 动态注册到路由表
- 模块无需手动注册
- 自动扫描
-
路由调用
- 标准化 URL 处理
- 拦截器链机制
- 参数自动合并
- 支持 completion 回调
-
商品模块调用流程
- 首页调用
openURL("jd://product/detail?id=123") - 拦截器检查登录态
- 执行商品模块的
routerHandle_Product_detail: - 创建并跳转到商品详情页
- 返回执行结果
- 首页调用
-
高级功能
- 路由调试工具
- 动态路由更新
- AOP 性能监控
这种设计实现了:
- 自动化注册:模块接口自动发现
- 安全调用:拦截器统一处理权限
- 灵活扩展:支持多种场景
- 高效解耦:模块间无直接依赖
问题:可以省去registerURL:handler: 方法,改为在 openURL: 方法中“直接解析”吗?
1. 关于 registerURL:handler: 方法
这个方法是 JDRouter 乃至所有路由组件的基石。它的核心作用是在路由表(一个字典)中建立一条映射规则,将某个特定的 URL 模式(Pattern)与一个处理该 URL 的代码块(Handler/Block)关联起来。
简单来说:它告诉路由系统:“当有人想打开这个 URL 时,请执行我给你的这段代码”。
2. 工作流程
-
注册阶段(App 启动时): 各个模块在自己的
+load或初始化方法中,调用registerURL:handler:,传入URL和handler。 -
存储: Router 将这对
URL: Handler以键值对的形式存储在一个内部的、全局的字典(路由表)中。 -
调用阶段(运行时): 当
openURL:被调用时,Router 会用传入的 URL 去路由表中查找匹配的handler。 -
执行: 如果找到,则执行该
handlerBlock,并将参数传递给它;如果没找到,则触发降级策略(如打开 H5 页)。
3. “先注册,后使用”模式的优点
-
解耦(Decoupling)
这是最核心的优点。调用方(如订单模块)和目标方(如商品详情页)完全不需要相互引用或导入头文件。它们唯一的联系就是一个约定好的字符串 URL(如jd://product/detail)。订单模块只知道这个 URL,并不知道背后是哪个 Class、哪个方法在处理它。这极大降低了模块间的依赖关系,使得模块可以独立开发、测试和替换。 -
集中式管理(Centralized Management)
路由表成了一个中心化的配置中心。你可以通过打印路由表,一目了然地看到整个 App 所有注册的、可被调用的界面和服务。这对于调试、新人熟悉项目、以及实现动态路由(服务端下发配置)至关重要。 -
灵活性(Flexibility)
一个 URL 的 Handler 可以执行任何操作,而不仅仅是创建 VC 和跳转。
跳转页面:最常用的功能。
执行业务逻辑:如jd://log/event?name=click_buy,触发一个埋点日志。
获取服务/对象:如jd://service/userInfo,返回当前用户信息的对象。
动态替换:基于 AB 测试或灰度发布,同一个 URL 在不同条件下可以注册不同的 Handler,指向不同的页面或实现。 -
安全与降级(Safety & Degradation)
Router 在openURL:时可以先进行统一拦截(Interceptor),进行登录态、权限等检查。如果检查不通过,可以中断跳转。
如果没有找到注册的 Handler,可以统一降级到一个默认的 H5 页面,保证链接不会失效,提升了应用的鲁棒性。 -
参数标准化(Parameter Standardization)
Handler 的签名是统一的(NSDictionary *params)。这使得参数的传递和解析变得标准化。Router 可以负责将 URL 中的 Query String、openURL:方法传入的params字典、甚至全局上下文参数进行合并,再交给 Handler,简化了开发。
4. 对比方案:直接解析 URL 并执行方法
“在 openURL: 中直接解析 URL,并查找目标类直接执行方法”,这通常被称为 “基于反射(Reflection)的方案”。可以实现吗?可以。 但这是一种弱类型、高风险、难维护的方案,在大型项目中被认为是一种反模式(Anti-Pattern)。
如何实现?
假设 URL 格式为:jd://TargetClass/actionName?key1=value1&key2=value2
在 openURL: 方法中:
- 解析出
TargetClass(目标类名)和actionName(方法名)。 - 使用
NSClassFromString(TargetClass)获取类。 - 使用
NSSelectorFromString(actionName)获取方法选择器。 - 使用
performSelector:或NSInvocation来调用方法。
为什么在大型项目中不推荐?
| 特性 | 注册模式 (JDRouter) | 反射模式 | 对比分析 |
|---|---|---|---|
| 安全性 | 高。只能调用已注册的、预期内的功能。 | 极低。任何类和方法都可以被调用,容易引发安全隐患和崩溃。 | 反射模式就像把家门钥匙给了所有人,而注册模式只给你想给的人。 |
| 可维护性 | 高。路由表清晰,易于查找和管理。 | 低。字符串散落在代码各处,无法集中管理,重构和查找困难。 | 想象一下,项目有几百个这样的字符串调用,改一个类名或方法名将是灾难。 |
| 类型安全 | 中高。Handler Block 的参数虽然是字典,但内部实现可以强类型检查。 | 无。所有参数都被打包成字符串,方法调用时无法进行编译期类型检查。 | 反射方案在编译期无法发现错误,所有错误(如方法不存在、参数类型错误)都会在运行时导致崩溃。 |
| 灵活性 | 高。Handler 可以做任何事情,不限于方法调用。 | 低。只能调用对象的方法,无法执行复杂的逻辑或条件判断。 | 注册模式的一个 Handler 里可以写任意代码,而反射模式只能调用一个现存的方法。 |
| 解耦程度 | 高。调用方对目标方无感知。 | 中。调用方需要知道目标类的字符串名称和方法名的字符串名称,这本身就是一种耦合。 | 反射模式并未真正解耦,只是将“硬依赖”变成了“软依赖”(字符串依赖),依赖关系依然存在且更难追踪。 |
| 性能 | 高。一次注册,之后就是高效的字典查询。 | 低。每次调用都需要进行字符串解析、运行时查找等操作,性能开销更大。 | 注册模式用空间(内存中的路由表)换时间(调用时的速度),是更优的选择。 |
一个简单的反射实现示例(及其问题)
- (BOOL)openURL:(NSString *)URLString {
// 1. 解析 URL(非常脆弱的解析)
NSArray *components = [URLString.path componentsSeparatedByString:@"/"];
NSString *targetClassName = components[1];
NSString *actionName = components[2];
// 2. 获取类和选择器(极不安全)
Class targetClass = NSClassFromString(targetClassName);
SEL actionSelector = NSSelectorFromString(actionName);
if (!targetClass || !actionSelector) {
return NO; // 容易崩溃,因为方法可能需要参数
}
// 3. 创建对象并调用方法(问题重重)
id target = [[targetClass alloc] init];
// 如何传递参数?参数是什么类型?方法返回值如何处理?
// 下面这行代码几乎一定会崩溃,因为方法大概率需要参数
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[target performSelector:actionSelector];
#pragma clang diagnostic pop
return YES;
}
上述代码的问题:
- 参数传递:无法优雅地传递 URL 中携带的参数。
-
初始化方法:默认调用
init,但目标类可能需要一个特定的初始化方法(如initWithProductId:)。 - 返回值:无法处理目标方法的返回值。
-
内存管理:
performSelector:可能导致内存泄漏问题。 - 方法签名:无法处理需要多个参数或复杂参数的方法。
所以,不建议省略注册方法而采用纯反射的方案。
registerURL:handler: 方法通过引入一个中间层(路由表) 和标准化协议(URL -> Handler),完美地解决了模块间调用时的耦合、安全和管理问题。
而纯粹的反射方案,虽然看起来“更动态”,但其带来的不安全性、脆弱的字符串依赖、极差的可维护性和高昂的运行时开销,使得它完全不适合大型、正规的工程项目。








网友评论