iOS 组件化方案 —— 路由Router
2023-06-19 本文已影响0人
大成小栈
Github地址:
https://github.com/DachengWang/XFRouter
比较典型的路由实现方案,如下:
Router.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
// 组件对外公开接口, m组件名, i接口名, p(arg)接收参数, c(callback)回调block
#define XFROUTER_EXTERN_METHOD(m, i, p, c) + (id) routerHandle_##m##_##i:(NSDictionary*)arg callback:(id)callback
@interface XFRouter : NSObject
+(void)openURL:(NSString *)stringUrl arg:(NSDictionary *)param error:(NSError *)error callBack:(void (^)(void))callBack;
@end
Router.m
#import "XFRouter.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation XFRouter
#pragma mark - public methods
+ (instancetype)sharedInstance {
static XFRouter *mediator;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
mediator = [[XFRouter alloc] init];
});
return mediator;
}
+(void)openURL:(NSString *)stringUrl arg:(NSDictionary *)param error:(NSError *)error callBack:(void (^)(void))callBack {
if (![stringUrl containsString:@"router://"]) {
NSLog(@"不合法的router");
return;
}
NSString *moduleStr = nil;
NSString *actionStr = nil;
if (!param) {
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
NSArray *urlElementsArrary = [stringUrl componentsSeparatedByString:@"//"];
NSString *pathStr = [urlElementsArrary lastObject];
if ([stringUrl containsString:@"?"]) {
NSArray *paramsArrary = [stringUrl componentsSeparatedByString:@"?"];
NSString *paramsStr = [paramsArrary lastObject];
for (NSString *paramStr in [paramsStr componentsSeparatedByString:@"&"]) {
NSArray *elts = [paramStr componentsSeparatedByString:@"="];
if([elts count] < 2) continue;
[params setObject:[elts lastObject] forKey:[elts firstObject]];
}
NSArray *pathStrArrary = [pathStr componentsSeparatedByString:@"?"];
pathStr = [pathStrArrary firstObject];
}
param = params;
NSArray *pathElementsArrary = [pathStr componentsSeparatedByString:@"/"];
moduleStr = [pathElementsArrary firstObject];
actionStr = [pathElementsArrary lastObject];
}
else {
if ([stringUrl containsString:@"?"]) {
NSLog(@"不合法的router");
return;
}
NSArray *urlElementsArrary = [stringUrl componentsSeparatedByString:@"//"];
NSString *pathStr = [urlElementsArrary lastObject];
NSArray *pathElementsArrary = [pathStr componentsSeparatedByString:@"/"];
moduleStr = [pathElementsArrary firstObject];
actionStr = [pathElementsArrary lastObject];
}
NSString *routerStr = [NSString stringWithFormat:@"routerHandle_%@_%@:callback:",moduleStr,actionStr];
Class targetClass = NSClassFromString(moduleStr);
SEL action = NSSelectorFromString(routerStr);
((void(*)(id,SEL,id,id))objc_msgSend)(targetClass, action,param,callBack);
if (callBack) {
callBack();
}
}
//如需增加参数参考以下方法
/*
- (id)safePerformAction:(SEL)action target:(NSObject *)target params:(NSDictionary *)params {
NSMethodSignature* methodSig = [target methodSignatureForSelector:action];
if(methodSig == nil) {
return nil;
}
const char* retType = [methodSig methodReturnType];
if (strcmp(retType, @encode(void)) == 0) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setArgument:¶ms atIndex:2];
[invocation setSelector:action];
[invocation setTarget:target];
[invocation invoke];
return nil;
}
if (strcmp(retType, @encode(NSInteger)) == 0) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setArgument:¶ms atIndex:2];
[invocation setSelector:action];
[invocation setTarget:target];
[invocation invoke];
NSInteger result = 0;
[invocation getReturnValue:&result];
return @(result);
}
if (strcmp(retType, @encode(BOOL)) == 0) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setArgument:¶ms atIndex:2];
[invocation setSelector:action];
[invocation setTarget:target];
[invocation invoke];
BOOL result = 0;
[invocation getReturnValue:&result];
return @(result);
}
if (strcmp(retType, @encode(CGFloat)) == 0) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setArgument:¶ms atIndex:2];
[invocation setSelector:action];
[invocation setTarget:target];
[invocation invoke];
CGFloat result = 0;
[invocation getReturnValue:&result];
return @(result);
}
if (strcmp(retType, @encode(NSUInteger)) == 0) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setArgument:¶ms atIndex:2];
[invocation setSelector:action];
[invocation setTarget:target];
[invocation invoke];
NSUInteger result = 0;
[invocation getReturnValue:&result];
return @(result);
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
return [target performSelector:action withObject:params];
#pragma clang diagnostic pop
}
*/
@end
下面,重温一下JDRouter中的过程:
-
组件原则
1)组件被定义为两种类型的组件:基础组件,业务组件。
2)基础组件可以被业务组件依赖,基础组件不可依赖业务组件。
3)业务组件不可依赖业务组件。 -
自定义协议
组件之间通信遵循一套自定义的协议通信,实现的方式网上有些开源的项目,我们综合各家实现考虑,最后定出的一个方案:组件间的通信应该是轻量级的,调用完就走,不留痕迹,不需要维护通信数据。
大致实现如下:
![](https://img.haomeiwen.com/i13180946/070ae4cce9869bd3.png)
实现一个JDRouter 的组件,用来实现组件与组件之间的通信。
注意:JDRouter只暴露一个头文件,其中两个方法:输入,输出(宏规范),以统一调用和回调方式。
//// 输入(A 调用B)
router://JDBClass/getString?name=Steven
//// 输出(B 被 A 调用)
+(id)getDataWithString:(NSString *)name {
NSString *str = [NSString stringWithFormat:@"HI, %@", name];
return str;
}
输入说明>> 通过 JDRouter 调用,类似于有这样一个方法,完成 a 到 b 的通信。
id g = [JDRouter openURL:@"router://JDBClass/getString?name=steven" arg:nil error:nil completion:nil];
输出说明>> module.m中的方法实现,及完成 a 到 b 的回调。使用宏替换,在 JDRouter 里提供一个输出的规范,这样在其他Module的.m中直接写一个类方法可以让 JDRouter 通过 URI 里的内容可以映射过去,输入/输出方式就得到了统一。
#define JDROUTER_EXTERN_METHOD(m,i,p,c) + (id) routerHandle_##m##_##i:(NSDictionary*)arg callback:(Completion)callback
将上面的类方法变为宏替换输出:
JDROUTER_EXTERN_METHOD(JDBClass, eat, arg, callback) {
NSString *name = arg[@"name"];
NSString *str = [NSString stringWithFormat:@"HI, %@", name];
return str;
}