JDRouter.h
#ifndef JDROUTER
#define JDROUTER
#import <Foundation/Foundation.h>
// 错误信息域
extern NSString *const __nonnull JDRouterErrorDomain;
// arg 字典key
extern NSString *const __nonnull JDRouterParamsOption;
// 处理调用完过程回调,可传任意oc对象
typedef void (^JDRouterCompletion)( id __nullable object);
// 注册openURL方法调用的全局block
typedef void (^JDRouterOpenURLCallback)( id __nonnull info);
// 组件对外公开接口, m组件名, i接口名, p(arg)接收参数, c(callback)回调block
#define JDROUTER_EXTERN_METHOD(m,i,p,c) + (id) routerHandle_##m##_##i:(NSDictionary*)arg callback:(JDRouterCompletion)callback
NS_ASSUME_NONNULL_BEGIN
@interface JDRouter : NSObject
/**
* 参数规则: URL中query与arg拼装出完整参数, query可不填,遵循URI get 参数规则, arg可为nil
* @param urlString - Scheme的URL,如 router://MyJD/userInfo?uid=123, 通过url query传入的参数获取为字典类型
* @param arg - arg 为任意oc对象, nil. 注意:如果arg为字典类型,
拼装结果要优先于query中相同字段(query中相同字段会被替换), 不为字典类型query有参数时以key为JDRouterParamsOption获取
* @param error - error 通信过程异常
* @param completion - completion 通信完后相应的callback, 业务输出方自行维护该block
*/
+ (nullable id)openURL:(nonnull NSString *)urlString arg:(nullable id)arg error:( NSError*__nullable *__nullable)error completion:(nullable JDRouterCompletion)completion;
/**
* 设置 JDRouter openURL 事件回调 Block
* @param openURLCallback JDRouter openURL 事件回调 Block
*/
+ (void)setOpenURLCallback:(nullable JDRouterOpenURLCallback)openURLCallback;
/**
* 解析OpenURL协议
*/
+ (NSDictionary*) extractParameterFromUrl:(NSString*)urlString;
@end
NS_ASSUME_NONNULL_END
#endif
JDRouter.m
// JDRouter.m
#import "JDRouter.h"
#import <objc/runtime.h>
// 错误域和常量定义
NSString *const JDRouterErrorDomain = @"JDRouterErrorDomain";
NSString *const JDRouterParamsOption = @"JDRouterParamsOption";
// 错误码定义
typedef NS_ENUM(NSInteger, JDRouterErrorCode) {
JDRouterErrorCodeNotFound = 404,
JDRouterErrorCodeInvalidURL = 400,
JDRouterErrorCodeModuleNotResponds = 500,
JDRouterErrorCodeExecutionFailed = 501
};
// 路由条目类
@interface JDRouterItem : NSObject
@property (nonatomic, copy) NSString *moduleName;
@property (nonatomic, copy) NSString *methodName;
@property (nonatomic, assign) Class moduleClass;
@property (nonatomic, copy) NSString *selectorString;
@end
@implementation JDRouterItem
@end
// JDRouter 实现
@interface JDRouter ()
@property (nonatomic, strong) NSMutableDictionary *routerMap;
@property (nonatomic, copy) JDRouterOpenURLCallback openURLCallback;
@end
@implementation JDRouter
+ (instancetype)sharedRouter {
static JDRouter *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[JDRouter alloc] init];
});
return sharedInstance;
}
- (instancetype)init {
self = [super init];
if (self) {
_routerMap = [NSMutableDictionary dictionary];
[self registerAllModules];
}
return self;
}
#pragma mark - 模块注册
// 自动注册所有模块
- (void)registerAllModules {
// 获取所有类
int numClasses = objc_getClassList(NULL, 0);
if (numClasses > 0) {
Class *classes = (Class *)malloc(sizeof(Class) * numClasses);
numClasses = objc_getClassList(classes, numClasses);
for (int i = 0; i < numClasses; i++) {
Class class = classes[i];
// 检查类是否响应 routerHandle_ 开头的方法
unsigned int methodCount = 0;
Method *methods = class_copyMethodList(object_getClass(class), &methodCount);
for (unsigned int j = 0; j < methodCount; j++) {
Method method = methods[j];
SEL selector = method_getName(method);
NSString *methodName = NSStringFromSelector(selector);
if ([methodName hasPrefix:@"routerHandle_"]) {
// 解析模块名和方法名
NSArray *components = [methodName componentsSeparatedByString:@"_"];
if (components.count >= 3) {
NSString *moduleName = components[1];
NSString *methodName = components[2];
// 注册路由
[self registerModule:moduleName method:methodName class:class];
}
}
}
free(methods);
}
free(classes);
}
}
// 注册模块和方法
- (void)registerModule:(NSString *)moduleName method:(NSString *)methodName class:(Class)moduleClass {
NSString *key = [self keyForModule:moduleName method:methodName];
JDRouterItem *item = [[JDRouterItem alloc] init];
item.moduleName = moduleName;
item.methodName = methodName;
item.moduleClass = moduleClass;
item.selectorString = [NSString stringWithFormat:@"routerHandle_%@_%@:callback:", moduleName, methodName];
self.routerMap[key] = item;
}
// 生成路由键
- (NSString *)keyForModule:(NSString *)moduleName method:(NSString *)methodName {
return [NSString stringWithFormat:@"%@.%@", moduleName, methodName];
}
#pragma mark - 公开方法
+ (nullable id)openURL:(NSString *)urlString arg:(nullable id)arg error:(NSError *__nullable *__nullable)error completion:(nullable JDRouterCompletion)completion {
return [[JDRouter sharedRouter] openURL:urlString arg:arg error:error completion:completion];
}
+ (void)setOpenURLCallback:(nullable JDRouterOpenURLCallback)openURLCallback {
[JDRouter sharedRouter].openURLCallback = openURLCallback;
}
+ (NSDictionary *)extractParameterFromUrl:(NSString *)urlString {
return [[JDRouter sharedRouter] extractParameterFromUrl:urlString];
}
#pragma mark - 核心实现
- (nullable id)openURL:(NSString *)urlString arg:(nullable id)arg error:(NSError *__nullable *__nullable)error completion:(nullable JDRouterCompletion)completion {
// 解析URL
NSURL *url = [NSURL URLWithString:urlString];
if (!url) {
if (error) *error = [NSError errorWithDomain:JDRouterErrorDomain
code:JDRouterErrorCodeInvalidURL
userInfo:@{NSLocalizedDescriptionKey: @"Invalid URL"}];
return nil;
}
// 提取模块名和方法名
NSString *moduleName = url.host;
NSString *methodName = [url.path stringByReplacingOccurrencesOfString:@"/" withString:@""];
if (!moduleName || !methodName) {
if (error) *error = [NSError errorWithDomain:JDRouterErrorDomain
code:JDRouterErrorCodeInvalidURL
userInfo:@{NSLocalizedDescriptionKey: @"Module or method not specified"}];
return nil;
}
// 查找路由项
NSString *key = [self keyForModule:moduleName method:methodName];
JDRouterItem *item = self.routerMap[key];
if (!item) {
if (error) *error = [NSError errorWithDomain:JDRouterErrorDomain
code:JDRouterErrorCodeNotFound
userInfo:@{NSLocalizedDescriptionKey: @"Module or method not found"}];
return nil;
}
// 检查类是否存在并响应方法
Class moduleClass = item.moduleClass;
SEL selector = NSSelectorFromString(item.selectorString);
if (![moduleClass respondsToSelector:selector]) {
if (error) *error = [NSError errorWithDomain:JDRouterErrorDomain
code:JDRouterErrorCodeModuleNotResponds
userInfo:@{NSLocalizedDescriptionKey: @"Module does not respond to selector"}];
return nil;
}
// 准备参数
NSDictionary *queryParams = [self extractParameterFromUrl:urlString];
NSMutableDictionary *finalParams = [NSMutableDictionary dictionaryWithDictionary:queryParams];
// 处理arg参数
if (arg) {
if ([arg isKindOfClass:[NSDictionary class]]) {
// 如果arg是字典,合并到参数中(覆盖query中的相同键)
[finalParams addEntriesFromDictionary:arg];
} else {
// 如果arg不是字典,将其作为特殊参数存储
finalParams[JDRouterParamsOption] = arg;
}
}
// 调用全局回调
if (self.openURLCallback) {
NSDictionary *info = @{
@"url": urlString,
@"module": moduleName,
@"method": methodName,
@"params": finalParams
};
self.openURLCallback(info);
}
// 使用NSInvocation调用方法
NSMethodSignature *signature = [moduleClass methodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setSelector:selector];
[invocation setTarget:moduleClass];
// 设置参数
[invocation setArgument:&finalParams atIndex:2];
[invocation setArgument:&completion atIndex:3];
// 调用方法
[invocation invoke];
// 获取返回值
id returnValue = nil;
if (signature.methodReturnLength > 0) {
[invocation getReturnValue:&returnValue];
}
return returnValue;
}
- (NSDictionary *)extractParameterFromUrl:(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.value) {
params[item.name] = item.value;
}
}
return [params copy];
}
@end
1. 路由注册机制
这个实现使用了自动注册机制,通过运行时API扫描所有类,查找符合routerHandle_ModuleName_MethodName:callback:
模式的方法,并自动注册到路由表中。
2. 参数处理逻辑
- URL参数解析:从URL的query部分提取参数
-
arg参数处理:
- 如果arg是字典,与URL参数合并(arg优先级更高)
- 如果arg不是字典,将其作为特殊参数存储在
JDRouterParamsOption
键下
3. 错误处理
定义了四种错误类型:
-
JDRouterErrorCodeNotFound
:模块或方法未找到 -
JDRouterErrorCodeInvalidURL
:URL格式错误 -
JDRouterErrorCodeModuleNotResponds
:模块不响应方法 -
JDRouterErrorCodeExecutionFailed
:方法执行失败
4. 回调机制
- JDRouterCompletion:由被调用模块在适当的时候调用,返回业务结果
- JDRouterOpenURLCallback:全局回调,每次路由调用时都会触发,用于日志记录和统计
5. 方法调用
使用NSInvocation
动态调用模块方法,这是Objective-C运行时的高级特性,可以处理各种参数和返回值类型。
使用示例
1. 定义模块方法
// 在模块实现文件中
JDROUTER_EXTERN_METHOD(MyModule, myMethod, arg, callback) {
NSString *param = arg[@"param"];
// 处理业务逻辑...
// 异步操作完成后回调
dispatch_async(dispatch_get_main_queue(), ^{
if (callback) {
callback(@"操作完成");
}
});
return nil;
}
2. 调用模块方法
// 在其他模块中调用
[JDRouter openURL:@"router://MyModule/myMethod?param=value"
arg:@{@"additionalParam": @YES}
error:nil
completion:^(id result) {
NSLog(@"操作结果: %@", result);
}];
3. 设置全局回调
// 在AppDelegate中设置
[JDRouter setOpenURLCallback:^(id info) {
NSLog(@"路由调用: %@", info);
// 这里可以进行日志记录或统计
}];
扩展建议
- 中间件支持:可以添加中间件机制,在路由调用前后执行特定逻辑(如权限检查、日志记录)
- 路由表持久化:可以将路由表持久化到文件,提高启动速度
- 性能优化:对于频繁调用的路由,可以缓存方法调用结果
- Swift兼容:添加Swift扩展,提供更友好的Swift API
这个实现提供了一个完整且功能丰富的路由器,适用于大型iOS应用中的模块化架构。