精简版 JDRouter

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);
    // 这里可以进行日志记录或统计
}];

扩展建议

  1. 中间件支持:可以添加中间件机制,在路由调用前后执行特定逻辑(如权限检查、日志记录)
  2. 路由表持久化:可以将路由表持久化到文件,提高启动速度
  3. 性能优化:对于频繁调用的路由,可以缓存方法调用结果
  4. Swift兼容:添加Swift扩展,提供更友好的Swift API

这个实现提供了一个完整且功能丰富的路由器,适用于大型iOS应用中的模块化架构。

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

推荐阅读更多精彩内容