1、這里是參考CTMediator實(shí)現(xiàn)的BSRouter,用runtime的方式動(dòng)態(tài)獲取url中傳遞的類和方法名字,通過(guò)組件配置好的target類實(shí)現(xiàn)組件間的值傳遞和頁(yè)面跳轉(zhuǎn)。
2、先說(shuō)使用方式
在組件中創(chuàng)建一個(gè)對(duì)應(yīng)的target類里邊設(shè)置好對(duì)外暴露的跳轉(zhuǎn)對(duì)應(yīng)頁(yè)面的方法action。
這樣其他人通過(guò)閱讀你寫(xiě)的readMe文件知道這個(gè)組件跳轉(zhuǎn)到那些頁(yè)面需要拼接的URL是什么,這只是簡(jiǎn)單的實(shí)現(xiàn),優(yōu)化后可以通過(guò)頁(yè)面ID更優(yōu)雅的配置和跳轉(zhuǎn)頁(yè)面。
下邊這段就是簡(jiǎn)單配置好之后 頁(yè)面的跳轉(zhuǎn)
代碼塊
UIViewController *viewController = [BSR() performActionWithUrl:[NSURL URLWithString:@"bs://Home/showVCWithParams?id=1234"] completion:nil];
[UIApplication sharedApplication].keyWindow.rootViewController = viewController;
3、說(shuō)下這個(gè)url的具體含義
bs://
沒(méi)有實(shí)際價(jià)值,可以用來(lái)規(guī)范 這就是一個(gè)正規(guī)的url
Home 對(duì)應(yīng)target的類名 類名是Target_Home
showVCWithParams 是對(duì)應(yīng)的方法名action 方法名是 Action_showVCWithParams:
?后邊是傳遞的值 有了一套規(guī)范就可以傳遞數(shù)據(jù)了。
4、既然說(shuō)完了調(diào)用方式,再說(shuō)說(shuō)使用Target的創(chuàng)建以Target_Home只實(shí)現(xiàn)了HomePageViewController這個(gè)頁(yè)面的跳轉(zhuǎn)。外部直接調(diào)取bs://Home/showVCWithParams?id=1234 就可以跳進(jìn)來(lái)。
這個(gè)類就是負(fù)責(zé)吧url轉(zhuǎn)化成控制器返回給調(diào)用者。
代碼塊
@interface Target_Home : NSObject
- (UIViewController *)Action_showVCWithParams:(NSDictionary *)params;
@end
- (UIViewController *)Action_showVCWithParams:(NSDictionary *)params
{
// 因?yàn)閍ction是從屬于ModuleA的,所以action直接可以使用ModuleA里的所有聲明
HomePageViewController *viewController = [[HomePageViewController alloc] init];
// viewController.userID = params[@"id"];
return viewController;
}
5、下邊是BSRouter的實(shí)現(xiàn)
代碼塊
//單例
+ (instancetype)sharedRouter;
// 遠(yuǎn)程App調(diào)用入口
- (id _Nullable)performActionWithUrl:(NSURL * _Nullable)url completion:(void(^_Nullable)(NSDictionary * _Nullable info))completion;
// 本地組件調(diào)用入口
- (id _Nullable )performTarget:(NSString * _Nullable)targetName action:(NSString * _Nullable)actionName params:(NSDictionary * _Nullable)params shouldCacheTarget:(BOOL)shouldCacheTarget;
- (void)releaseCachedTargetWithFullTargetName:(NSString * _Nullable)fullTargetName;
// 簡(jiǎn)化調(diào)用單例的函數(shù)
BSRouter* _Nonnull BSR(void);
/*
scheme://[target]/[action]?[params]
url sample:
aaa://targetA/actionB?id=1234
*/
- (id)performActionWithUrl:(NSURL *)url completion:(void (^)(NSDictionary *))completion
{
if (url == nil||![url isKindOfClass:[NSURL class]]) {
return nil;
}
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
NSURLComponents *urlComponents = [[NSURLComponents alloc] initWithString:url.absoluteString];
// 遍歷所有參數(shù)
[urlComponents.queryItems enumerateObjectsUsingBlock:^(NSURLQueryItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (obj.value&&obj.name) {
[params setObject:obj.value forKey:obj.name];
}
}];
// 這里這么寫(xiě)主要是出于安全考慮,防止黑客通過(guò)遠(yuǎn)程方式調(diào)用本地模塊。這里的做法足以應(yīng)對(duì)絕大多數(shù)場(chǎng)景,如果要求更加嚴(yán)苛,也可以做更加復(fù)雜的安全邏輯。
NSString *actionName = [url.path stringByReplacingOccurrencesOfString:@"/" withString:@""];
if ([actionName hasPrefix:@"native"]) {
return @(NO);
}
// 這個(gè)demo針對(duì)URL的路由處理非常簡(jiǎn)單,就只是取對(duì)應(yīng)的target名字和method名字,但這已經(jīng)足以應(yīng)對(duì)絕大部份需求。如果需要拓展,可以在這個(gè)方法調(diào)用之前加入完整的路由邏輯
id result = [self performTarget:url.host action:actionName params:params shouldCacheTarget:NO];
if (completion) {
if (result) {
completion(@{@"result":result});
} else {
completion(nil);
}
}
return result;
}
- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget
{
if (targetName == nil || actionName == nil) {
return nil;
}
NSString *swiftModuleName = params[kSwiftTargetModuleName];
// generate target
NSString *targetClassString = nil;
if (swiftModuleName.length > 0) {
targetClassString = [NSString stringWithFormat:@"%@.Target_%@", swiftModuleName, targetName];
} else {
targetClassString = [NSString stringWithFormat:@"Target_%@", targetName];
}
NSObject *target = [self safeFetchCachedTarget:targetClassString];
if (target == nil) {
Class targetClass = NSClassFromString(targetClassString);
target = [[targetClass alloc] init];
}
// generate action
NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName];
SEL action = NSSelectorFromString(actionString);
if (target == nil) {
// 這里是處理無(wú)響應(yīng)請(qǐng)求的地方之一,這個(gè)demo做得比較簡(jiǎn)單,如果沒(méi)有可以響應(yīng)的target,就直接return了。實(shí)際開(kāi)發(fā)過(guò)程中是可以事先給一個(gè)固定的target專門(mén)用于在這個(gè)時(shí)候頂上,然后處理這種請(qǐng)求的
[self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
return nil;
}
if (shouldCacheTarget) {
[self safeSetCachedTarget:target key:targetClassString];
}
if ([target respondsToSelector:action]) {
return [self safePerformAction:action target:target params:params];
} else {
// 這里是處理無(wú)響應(yīng)請(qǐng)求的地方,如果無(wú)響應(yīng),則嘗試調(diào)用對(duì)應(yīng)target的notFound方法統(tǒng)一處理
SEL action = NSSelectorFromString(@"notFound:");
if ([target respondsToSelector:action]) {
return [self safePerformAction:action target:target params:params];
} else {
// 這里也是處理無(wú)響應(yīng)請(qǐng)求的地方,在notFound都沒(méi)有的時(shí)候,這個(gè)demo是直接return了。實(shí)際開(kāi)發(fā)過(guò)程中,可以用前面提到的固定的target頂上的。
[self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
@synchronized (self) {
[self.cachedTarget removeObjectForKey:targetClassString];
}
return nil;
}
}
}
- (void)releaseCachedTargetWithFullTargetName:(NSString *)fullTargetName
{
/*
fullTargetName在oc環(huán)境下,就是Target_XXXX。要帶上Target_前綴。在swift環(huán)境下,就是XXXModule.Target_YYY。不光要帶上Target_前綴,還要帶上模塊名。
*/
if (fullTargetName == nil) {
return;
}
@synchronized (self) {
[self.cachedTarget removeObjectForKey:fullTargetName];
}
}
#pragma mark - private methods
- (void)NoTargetActionResponseWithTargetString:(NSString *)targetString selectorString:(NSString *)selectorString originParams:(NSDictionary *)originParams
{
SEL action = NSSelectorFromString(@"Action_response:");
NSObject *target = [[NSClassFromString(@"Target_NoTargetAction") alloc] init];
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
params[@"originParams"] = originParams;
params[@"targetString"] = targetString;
params[@"selectorString"] = selectorString;
[self safePerformAction:action target:target params:params];
}
- (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
}
BSRouter* _Nonnull BSR(void){
return [BSRouter sharedRouter];
};