传统模式的弊端
传统模式的组件之间的交互都是#import "HomeVC.h"
,然后在该页面push
或者present
,项目小没有问题。但是当项目越来越大,就会发现:
- 模块间相互依赖,耦合严重
- 管理起来很混乱
如下图:
任何一个模块变动,导致其他依赖该模块的地方需要检查是否要随着变动。
那么按照解耦的思想,改成如下这种中心化的方式就会清晰明了
但是依然有问题。虽然看起来比刚开始清晰了很多,但是每个组件
还是和中间层
双向依赖
双向依赖:如果首页或者登陆改变,导致依赖这两个模块的
中间层
可能也需要改变,还是具有耦合性,只是低了一些
所以target-action
设计模式就解决了这些问题
我们把这里的中间层
叫做Mediator
但是有几个问题需要这个设计模式处理:
- Mediator如何找到并调用组件
- 相互独立的组件如何调用其他组件的方法
首先每个模块需要配置Target和Category,其中Target是每个组件对应一个或者多个Target,Target可以引用和依赖组件,Category是MEdiator的分类,使用分类是为了让Mediator中的业务代码分离,不然所有代码在Mediator中会导致其臃肿
1. Mediator如何找到组件、调用组件
Target中引用了组件,所以Mediator找到target就可以了,Category利用runtime的API,通过字符串的形式创建Target对象,以及创建Selector,再把Target和Selector交由Mediator主类去调用Action
大概目录结构如下图:
Demo演示
//OBMediator.h
@interface OBMediator : NSObject
+(OBMediator *)shared;
- (void)performTarget:(NSString *)targetName Action:(NSString *)actionName Params:(NSDictionary *)parmas;
@end
//OBMediator.m
#import "OBMediator.h"
#import "ViewController.h"
//#import "PersonalVC.h"
@implementation OBMediator
+(OBMediator *)shared {
static OBMediator *mediator = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
mediator = [[OBMediator alloc] init];
});
return mediator;
}
- (void)performTarget:(NSString *)targetName Action:(NSString *)actionName Params:(NSDictionary *)parmas {
Class cls = NSClassFromString(targetName);
NSObject *target = [[cls alloc] init];
SEL sel = NSSelectorFromString(actionName);
if (![target respondsToSelector:sel]) {
NSLog(@"未知错误");
return ;
}
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[target performSelector:sel withObject:parmas];
}
@end
target
@interface Target_Personal : NSObject
@end
#import "Target_Personal.h"
#import "PersonalVC.h"
@implementation Target_Personal
- (void)pushToPersonalVCWithParams:(NSDictionary *)parmas {
PersonalVC * vc =[[PersonalVC alloc] init];
[[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:vc animated:YES completion:nil];
}
@end
OBMediator+Personal.h类中的实现
// OBMediator+Personal.h
#import "OBMediator.h"
@interface OBMediator (Personal)
- (void)openPersonalVCWithParams:(NSDictionary *)parmas;
@end
//OBMediator+Personal.m
#import "OBMediator+Personal.h"
NSString * const PersonalTarget = @"Target_Personal";
NSString * const ActionPushPersonal = @"pushToPersonalVCWithParams:";
@implementation OBMediator (Personal)
- (void)openPersonalVCWithParams:(NSDictionary *)parmas {
//内部通过Target_Personal字符串找到该对象,执行他的selector,就是找到最终的跳转代码
[self performTarget:PersonalTarget Action:ActionPushPersonal Params:parmas];
}
@end
调用时需要引入OBMediator+Personal.h
#import "OBMediator+Personal.h"
- (void)click {
[[OBMediator shared] openPersonalVCWithParams:@{@"key":@"value"}];
}
Target-Action方案的优点。
- 解耦:只存在组件依赖Mediator中介的这一层依赖关系。
- Target-Action方案也能有一定的安全保证,它对URL中的Native前缀进行安全验证。因此,Target-Action方案不管从维护性、可读性、扩展性来说,都是一个比较完美的方案。
- 统一处理所有组件间调用入口,方便管理:都是调用“performTarget: action: params: shouldCacheTarget:”方法。第三个参数是一个字典,这个字典里面可以传很多参数,只要Key-Value写好就可以了。
Target-Action方案处理错误的方式也统一在一个地方了,Target没有,或者是Target无法响应的Action方法,都可以在Mediator这里进行统一出错处理。
Target-Action方案的缺点
- 每个组件的Category对应一个Target类,Categroy中的Action方法对应Target类中的Action场景。代码量增多
- 伪解耦,通过字符串的方式来创建实例,依赖各种定义的String,维护String