问题: 简述一下当前流行的组件化方案.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

IT界无底坑洞栋主 欢迎加Q骚扰:676758285