[架构]iOS跳转中心探索之路

iOS跳转中心探索之路

1.面临的问题与背景

1.1 如果没有跳转中心时, 工程内组件件是如何调用的?

在项目中, 总是会有各个模块之间的相互调用与耦合. 一般情况是这样的: 没跳转中心时的场景

1.2 加入跳转中心后

各个模块之间通过Mediator/Route来实现相互调用。 加入跳转中心后的场景

疑问:
好像并没有什么区别?只是将耦合放到了Mediator层,

1.3 跳转中心愿景

一个模块只与Mediator耦合,不与外部模块耦合

3个问题:

  • 只与Mediator耦合,如何知道别的模块提供了什么接口?
  • 如何实现跨组件调用?
  • 如何破除Mediator与模块间的耦合?

2、业内方案对比

2.1 URL -> Block

代码推演:
核心思想: 将URL与Block一一绑定. 在使用的时候只使用URL, 就可以调用到对应的Block.

核心模块Code:

// 跳转中心模块
// 注册
- (void)registerMediatorWithMoudle:(NSString *)moudle eventBlock:(EventBlock)block {
    [self.eventDict setObject:block forKey:moudle];
}

// 打开
- (id)openUrl:(NSString *)urlStr {
    NSLog(@"URLMediator openUrl : %@", urlStr);
    
    NSRange spaceRange = [urlStr rangeOfString:@"?"];
    if (spaceRange.location == NSNotFound) {
        NSLog(@"URLMediator 不支持该url");
        return nil;
    }
    
    NSString *moudle = [urlStr substringToIndex:spaceRange.location];
    EventBlock block = self.eventDict[moudle];
    
    if (block) {
        NSDictionary *param = [urlStr parameterFromUrlStr];
        return block(param);
    } else {
        NSLog(@"URLMediator openUrl Error.");
    }
    
    return nil;
}
// 组件方接入
@implementation AMediatorRegister

+ (void)load {
    [[URLMediator sharedInstance] registerMediatorWithMoudle:@"suyun://A" eventBlock:^id(NSDictionary *param) {
        NSLog(@"A组件被调用了");
        
        AViewController *aVC = [[AViewController alloc] init];
        
        NSString *labelStr = param[@"labelStr"];
        if (labelStr) {
            [aVC setLabelStr:labelStr];
        }
        
        NSString *jump = param[@"jump"];
        if ([jump integerValue] == 1) {
            [kRootNavigation pushViewController:aVC animated:YES];
        }
        
        return aVC;
    }];
}
// 调用
[[URLMediator sharedInstance] openUrl: @"suyun://A?jump=1&labelStr=fdafs"];

类图:

URL->Block类图

存在的问题:

  • Block内存常驻, 不能释放
  • 本地调用需要使用转URL的方式, 容易出错
  • 只能传可字符串的数据

解决方案:

  • 问题1: 无法解决
  • 问题2: 通过文档规范
  • 问题3: 无法解决

远程跳转与本地跳转说明:

远程跳转: URL方式调整, 例: suyun://homeVC?jump=1&title=aaa
本地跳转: 类似于方法调用, 可以包含特殊参数
URL方式就不能传非常规数据类型, 比如UIImage, NSData, 自定义类. 只能传可被URL化的数据, 所以URL->Block的方式也就限制了只能传常规参数.

<img src="https://raw.githubusercontent.com/ChenTF/Blog/master/Mediator/Resource/2_2.jpg" alt="远程调用与本地调用关系图" height=180px width=300px />

2.2 Target-Action  

2.2.1 直接使用RunTime的场景

    Class class = NSClassFromString(@"AViewController");
    SEL sel = NSSelectorFromString(@"setLabelStr:");
    
    UIViewController *vc = [[class alloc] init];
    [vc performSelector:sel withObject:@"aaa"];
    
    [self.navigationController pushViewController:vc animated:YES];

存在的缺点:

  • 类名, 方法名, 参数不明确, 容易出错;
  • 使用方极其不方便, 需要写一大堆代码;

优化点:

  • RunTime调用的部分是通用的, 可以抽离到一个类中
  • 在这个类中添加安全处理, 如class为nil, sel不存在
  • 使用Category类对Mediator进行扩充, Mediator+MouduleName
  • 使用Target_MouduleName来提取组件接口

2.2.2 优化后

核心模块代码:

// 跳转中心 Mediator
- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget
{
    
    NSString *targetClassString = [NSString stringWithFormat:@"Target_%@", targetName];
    NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName];
    Class targetClass;
    
    NSObject *target = self.cachedTarget[targetClassString];
    if (target == nil) {
        targetClass = NSClassFromString(targetClassString);
        target = [[targetClass alloc] init];
    }
    
    SEL action = NSSelectorFromString(actionString);
    
    if (target == nil) {
        // 这里是处理无响应请求的地方之一,这个demo做得比较简单,如果没有可以响应的target,就直接return了。实际开发过程中是可以事先给一个固定的target专门用于在这个时候顶上,然后处理这种请求的
        return nil;
    }
    
    if (shouldCacheTarget) {
        self.cachedTarget[targetClassString] = target;
    }

    if ([target respondsToSelector:action]) {
        return [self safePerformAction:action target:target params:params];
    }
}
// 组件A的接口 Mediator+A 
NSString * const kCTMediatorTargetA = @"A";

NSString * const kCTMediatorAction_AVC = @"AVC";

@implementation CTMediator (A)

- (void)CTMediator_AWithOpen:(BOOL)open
                    LabelStr:(NSString *)labelStr {
    NSMutableDictionary *paramsToSend = [[NSMutableDictionary alloc] init];
    
    if (open) {
        paramsToSend[@"open"] = @"1";
    }
    if (labelStr) {
        paramsToSend[@"labelStr"] = labelStr;
    }
    
    [self performTarget:kCTMediatorTargetA action:kCTMediatorAction_AVC params:paramsToSend shouldCacheTarget:NO];
}
// 组件A接口的实现 Target_A
@implementation Target_A

- (id)Action_AVC:(NSDictionary *)params {
    AViewController *aVC = [[AViewController alloc] init];
    
    NSString *labelStr = params[@"labelStr"];
    if (labelStr) {
        [aVC setLabelStr:labelStr];
    }
    
    NSString *open = params[@"open"];
    if ([open integerValue] == 1) {
        [kRootNavigation pushViewController:aVC animated:YES];
    }
    
    return aVC;
}
// 使用方
// 1.本地调用
[[CTMediator sharedInstance] CTMediator_AWithOpen:YES LabelStr:@"fsfda”];
// 2.远程调用
[[CTMediator sharedInstance] dispatchCenterWithUrlStr:@“suyun://xxx"];

类图:

CTMediator类图

存在的缺点:

  • Moudle中的调用在Target_A中需要再写一遍.

如何实现远程调用?
本组件的重点是先实现本地调用, 远程调用是将对应的URL进行解析后, 进行的转换, 分发实现的.

2.3 LJRoute

核心思想:
使用宏方法来重写系统方法, 在宏方法内注册组件, 将对应的类名, 方法名保存到跳转中心中. 在使用时通过RunTime+对应的名称来实现实例化与调用.

内部调用
<img src="https://raw.githubusercontent.com/ChenTF/Blog/master/Mediator/Resource/2_4.jpg" lat="LJRoute内部调用方法" width="600" height="350">

存在的问题:

  • 接入成功过高: 每次写代码需要使用宏方法, 而不是原方法. 修改了原有编码习惯.
  • 业务侵入太深: 跳转中心与组件完全绑定了, 后续如果有修改, 将是灾难

优化点:

  • 采用无侵入的方式来实现

3. Target-Action在当前业务中应用效果

3.1 本地跳转<span id="#3.1"></span>

// CTmediator+Login.h
@interface CTMediator (Login)

- (void)CTMediator_jumpToLoginVC;

@end

// CTmediator+Charge.h
@interface CTMediator (Charge)

/**
 生成收费页
 
 @param orderID 订单id
 @return <#return value description#>
 */
- (UIViewController *)CTMediator_beforeHandSettleVCWithOrderID:(NSString *)orderID;

3.2 远程跳转

// CTmediator+SYDispatch.h
@interface CTMediator (SYDispatch)

/**
 跳转中心(会直接进行跳转)

 @param urlStr urlStr
 @return YES(有处理)/NO(无处理)
 */
- (BOOL)dispatchCenterWithUrlStr:(NSString *)urlStr;

@end

4.未来的愿景

4.1 横向切分与纵向切分关系

横向拆分业务、功能模块: 横向拆分

纵向拆分技术、架构模块: 纵向拆分

5 相关参考:

casa 的 CTMediator
蘑菇街 App 的组件化之路
iOS组件化方案探索
模块化与组件化

转载于:https://my.oschina.net/ChenTF/blog/1864083

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值