问题: 简述一下当前流行的组件化方案.0
-
当前组件化中间件方案主要有三种.
-
URL-Block
-
Protocol-Class
-
Target-Action
-
问: 简述一下 URL-Block 中间件相关内容.
服务方提前在中间件注册 URL - Block, 中间件以URL为key, Block为value进行存储保存.调用方以URL形式调用中间件的方法,从而调用Block.
代表: MGJRouter
优点:
- 方案成熟, 极高的动态性, 能解决组件依赖问题, 易于适配URL Scheme.
- 可多端复用 (方便统一管理多平台的路由规则)
缺点:
- 注册过程中会造成内存常驻问题, Block使用不当会造成循环引用问题.
- 解耦能力有限,这是由于服务方和使用方都需要依赖中间件造成的.
- 服务方需要提前注册,否则调用方可能调用失败.
- URL硬编码可能会导致调用组件失败.
注: 在蘑菇街组件化架构中,存在了很多硬编码的URL和参数。在代码实现过程中URL编写出错会导致调用失败,而且参数是一个字典类型,调用方不知道服务方需要哪些参数,这些都是个问题。
整体示例如下所示.
// URLBlockRouter.h
#import <Foundation/Foundation.h>
typedef void(^RouterBlock)(NSDictionary *params);
@interface URLBlockRouter : NSObject
+ (instancetype)sharedInstance;
// 注册
- (void)registerURL:(NSString *)URL block:(RouterBlock)block;
// 调用
- (void)excuteBlockWithURL:(NSString *)URL params:(NSDictionary *)params;
@end
// URLBlockRouter.m
#import "URLBlockRouter.h"
@interface URLBlockRouter ()
@property(nonatomic, strong) NSMutableDictionary *URLDictionary;
@end
@implementation URLBlockRouter
+ (instancetype)sharedInstance {
static URLBlockRouter *router = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
router = [[super allocWithZone:NULL] init];
});
return router;
}
// 防止外部调用alloc 或者 new
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
return [URLBlockRouter sharedInstance];
}
// 防止外部调用copy
- (id)copyWithZone:(nullable NSZone *)zone {
return [URLBlockRouter sharedInstance];
}
// 防止外部调用mutableCopy
- (id)mutableCopyWithZone:(nullable NSZone *)zone {
return [URLBlockRouter sharedInstance];
}
// 注册
- (void)registerURL:(NSString *)URL block:(RouterBlock)block {
if (!URL || !block) return;
self.URLDictionary[URL] = block;
}
// 调用
- (void)excuteBlockWithURL:(NSString *)URL params:(NSDictionary *)params {
if (!URL) return;
RouterBlock block = self.URLDictionary[URL];
if (!block) return;
block(params);
}
- (NSMutableDictionary *)URLDictionary {
if (_URLDictionary == nil) {
_URLDictionary = [NSMutableDictionary dictionary];
}
return _URLDictionary;
}
@end
// AModule.h
#import <Foundation/Foundation.h>
@interface AModule : NSObject
@end
// AModule.m
#import "AModule.h"
#import "URLBlockRouter.h"
@implementation AModule
- (instancetype)init {
if (self = [super init]) {
[[URLBlockRouter sharedInstance] registerURL:@"A:test" block:^(NSDictionary * _Nonnull params) {
[self testAction:params];
}];
}
return self;
}
- (void)testAction:(NSDictionary *)params {
NSLog(@"%@", params);
}
@end
// BModule.h
#import <Foundation/Foundation.h>
@interface BModule : NSObject
@end
这里我们假设调用 BModule init
方法内部会调用Router的 excuteBlockWithURL:params:
.
// BModule.m
#import "BModule.h"
#import "URLBlockRouter.h"
@implementation BModule
- (instancetype)init {
if (self = [super init]) {
[[URLBlockRouter sharedInstance] excuteBlockWithURL:@"A:test" params:@{@"key":@"123"}];
}
return self;
}
@end
问: 简述一下 Protocol-Class 中间件相关内容.
服务方提前在中间件注册 Protocol - Class, 中间件以Protocol为key, Class为value进行存储保存.调用方以Protocol调用中间件的方法,从而获取Class. 然后再通过创建实例对象,通过实例对象调用协议方法,完成整个调度.
优点:
URL硬编码
和参数校验
问题得到解决. 在编译过程中就能及时发现参数问题.- 接口与实现得到了分离.
缺点:
- 仍然存在内存常驻问题.
- 只能做 potocol 与 class 的匹配,不支持更加复杂的创建方式和依赖注入.
- 由框架创建所有的对象,创建方式有限,例如不支持外部传入参数,再调用自定义初始化.
- 仍然无法保证所使用的的 potocol 一定存在模块, 也无法直接判断某个 protocol 是否能用于获取模块。
- 调用方和服务方仍然都需要依赖中间件.
整体示例Demo如下所示.
// ProtocolClassRouter.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
//协议
@protocol AService <NSObject>
- (void)protocolTestAction:(NSString *)text;
@end
@interface ProtocolClassRouter : NSObject
+ (instancetype)sharedInstance;
// 注册
- (void)registerProtocol:(Protocol *)protocol regiterClass:(Class)regiterClass;
// 调用
- (id)getObjectWithProtocol:(Protocol *)protocol;
@end
NS_ASSUME_NONNULL_END
// ProtocolClassRouter.m
#import "ProtocolClassRouter.h"
@interface ProtocolClassRouter ()
@property(nonatomic, strong) NSMutableDictionary *protocolDictionary;
@end
@implementation ProtocolClassRouter
+ (instancetype)sharedInstance {
static ProtocolClassRouter *router = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
router = [[super allocWithZone:NULL] init];
});
return router;
}
// 防止外部调用alloc 或者 new
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
return [ProtocolClassRouter sharedInstance];
}
// 防止外部调用copy
- (id)copyWithZone:(nullable NSZone *)zone {
return [ProtocolClassRouter sharedInstance];
}
// 防止外部调用mutableCopy
- (id)mutableCopyWithZone:(nullable NSZone *)zone {
return [ProtocolClassRouter sharedInstance];
}
// 注册
- (void)registerProtocol:(Protocol *)protocol regiterClass:(Class)regiterClass {
if (!protocol || !regiterClass) {
return;
}
self.protocolDictionary[NSStringFromProtocol(protocol)] = regiterClass;
}
// 调用
- (id)getObjectWithProtocol:(Protocol *)protocol {
if (!protocol) {
return nil;
}
Class cls = self.protocolDictionary[NSStringFromProtocol(protocol)];
id obj = [cls new];
if ([obj conformsToProtocol:protocol]) {
return obj;
}
return nil;
}
- (NSMutableDictionary *)protocolDictionary {
if (_protocolDictionary == nil) {
_protocolDictionary = [NSMutableDictionary dictionary];
}
return _protocolDictionary;
}
@end
// AModule.h
#import <Foundation/Foundation.h>
@interface AModule : NSObject
@end
// AModule.m
#import "AModule.h"
#import "ProtocolClassRouter.h"
@interface AModule ()<AService>
@end
@implementation AModule
- (instancetype)init {
if (self = [super init]) {
[[ProtocolClassRouter sharedInstance] registerProtocol:@protocol(AService) regiterClass:self.class];
}
return self;
}
- (void)protocolTestAction:(NSString *)text {
NSLog(@"%@", text);
}
@end
// BModule.h
#import <Foundation/Foundation.h>
@interface BModule : NSObject
@end
这里我们假设调用 BModule init
方法内部会调用Router的 getObjectWithProtocol:
.
// BModule.m
#import "BModule.h"
#import "ProtocolClassRouter.h"
@implementation BModule
- (instancetype)init {
if (self = [super init]) {
id<AService> obj = [[ProtocolClassRouter sharedInstance] getObjectWithProtocol:@protocol(AService)];
[obj protocolTestAction:@"1111"];
}
return self;
}
@end
问: 简述一下 Target-Action 中间件相关内容.
服务方不需要再进行注册,调用方通过中间件的方法运用Runtime的performSelector:withObject:
调起服务方的相关方法.
代表: CTMediator
优点:
- 不需要注册和内存占用.
缺点:
- 编译时无法及时发现Bug, 必须要遵循命名规范.
- 无法保证所使用的模块一定存在, target模块修改完成之后,只有调用方运行过程中才能发现问题.
- 过于依赖 runtime 特性,无法应用到纯 Swift 上。在 Swift 中扩展 mediator 时,无法使用纯 Swift 类型的参数
- 使用过多的runtime,苹果审核可能会有问题.
- 仍然是字符串编码,问题可能和URL-Block中间件的缺点一样.
整体实现代码如下所示.
// TargetActionRouter.h
#import <Foundation/Foundation.h>
@interface TargetActionRouter : NSObject
+ (instancetype)sharedInstance;
/// 调用方法
/// @param target 类
/// @param action 方法
/// @param params 参数
- (id)performWithTarget:(NSString *)target action:(NSString *)action params:(NSDictionary *)params;
@end
// TargetActionRouter.m
#import "TargetActionRouter.h"
@implementation TargetActionRouter
+ (instancetype)sharedInstance {
static TargetActionRouter *router = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
router = [[super allocWithZone:NULL] init];
});
return router;
}
// 防止外部调用alloc 或者 new
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
return [TargetActionRouter sharedInstance];
}
// 防止外部调用copy
- (id)copyWithZone:(nullable NSZone *)zone {
return [TargetActionRouter sharedInstance];
}
// 防止外部调用mutableCopy
- (id)mutableCopyWithZone:(nullable NSZone *)zone {
return [TargetActionRouter sharedInstance];
}
/// 调用方法
/// @param target 类
/// @param action 方法
/// @param params 参数
- (id)performWithTarget:(NSString *)target action:(NSString *)action params:(NSDictionary *)params {
Class cls;
id object;
SEL sel;
cls = NSClassFromString(target);
if (cls == nil) {
return nil;
}
sel = NSSelectorFromString(action);
if (sel == nil) {
return nil;
}
object = [cls new];
if (![object respondsToSelector:sel]) {
return nil;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
return [object performSelector:sel withObject:params];
#pragma clang diagnostic pop
}
@end
// AModule.h
#import <Foundation/Foundation.h>
@interface AModule : NSObject
@end
// AModule.m
#import "AModule.h"
@implementation AModule
- (void)testAction:(NSDictionary *)params {
NSLog(@"%@", params);
}
@end
// BModule.h
#import <Foundation/Foundation.h>
@interface BModule : NSObject
@end
这里我们假设调用 BModule init
方法内部会调用Router的 performWithTarget:action:params:
.
// BModule.m
#import "BModule.h"
#import "TargetActionRouter.h"
@implementation BModule
- (instancetype)init {
if (self = [super init]) {
[[TargetActionRouter sharedInstance] performWithTarget:@"AModule" action:@"testAction:" params:@{@"key":@"123"}];
}
return self;
}
@end
Comments | 0 条评论