『iOS』如何设计一个好的插件(策略模式)

如何为设计一个可以好的插件库
我觉得,一个好的插件库,首先必须不能跟其他组件库耦合,那么该如何设计呢?
我觉得如果要设计好这个东西,必不可少的那就是协议,恰当的来说是使用了策略模式。
下面就用为flutter设计的插件来说一下,应该怎么来做。
flutter与原生之间的交互式用channel来实现的,当然可以不用太在意这些东西,看思路就好。

+ (void)registerWithRegistrar:(NSObject*)registrar {
    FlutterMethodChannel* channel = [FlutterMethodChannel
                                     methodChannelWithName:@"cn.com.zhaopin.flutter_base_plugin"
                                     binaryMessenger:[registrar messenger]];
    FlutterBasePlugin* instance = [[FlutterBasePlugin alloc] init];
    [registrar addMethodCallDelegate:instance channel:channel];
}

比如我现在要实现几个功能,分别是埋点上报,错误捕获,图片浏览,图片选择。
因为通过channel来实现交互,然后通过handleMethodCall方法来下发flutter调用的方法。
最简单的方法,我们可以在FlutterBasePlugin中,简单的通过if else在handleMethodCall方法中实现。当然不会采用那种方式。
这里可以利用字典来设计一个key:value对应的方法表。

- (NSDictionary *)strategyMapDic {
    if (nil == _strategyMapDic) {
        _strategyMapDic = @{
            @"analyticsReport": @"ZPFlutterBaseTrackStrategy",              // 埋点上报
            @"catchException": @"ZPFlutterBaseCatcherStrategy",             // 错误捕获
            @"browsePic": @"ZPFlutterBaseImageBrowserStrategy",             // 图片浏览
            @"pickPhoto": @"ZPFlutterBaseImagePickerStrategy",              // 图片选择
            @"nativeDataMethod": @"ZPFlutterBaseNativeDataStrategy",        // 获取原生数据
        };
    }
    return _strategyMapDic;
}

ZPFlutterBaseTrackStrategy, ZPFlutterBaseCatcherStrategy, ZPFlutterBaseImageBrowserStrategy, ZPFlutterBaseImagePickerStrategy 这几个类,分别对应着不同的策略中转类。

- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
        
    NSString *className = [self.strategyMapDic objectForKey:call.method];
    if (className.length > 0) {
        Class cls = NSClassFromString(className);
        if (cls) {
            id strategy = [[cls alloc] init];
            ZPFlutterBaseContext *context = [[ZPFlutterBaseContext alloc] initWithStrategy:strategy];
            [context executeStrategyWithArguments:call.arguments completion:^(NSString * _Nonnull jsonString) {
                result(jsonString);
            }];
            return;
        }
    }
    
    NSString *jsonString = [ZhaopinFlutterBaseManager formatFailureResultWithData:@{} code:@"-2" errorMsg:@"未找到方法实现"];
    result(jsonString);
}

这里需要一个中转类和一个中转协议,把flutter下发的方法,分别转发出去。仔细想了一下,这里用策略模式是很合适的。

/// 策略协议
@protocol ZPFlutterBaseStrategyProtocol 

@required

/// 埋点查找实现类并调用
/// @param arguments flutter 参数
/// @param completion 调用完成回调
- (void)arguments:(NSDictionary *)arguments completion:(void (^)(id result))completion;

@end

@interface ZPFlutterBaseContext()

@property(nonatomic, strong) id strategy;


@end

@implementation ZPFlutterBaseContext


/// 初始化
/// @param strategy 策略实现
- (instancetype)initWithStrategy:(id) strategy {
    self = [super init];
    if (self) {
        self.strategy = strategy;
    }
    return self;
}

/// 执行策略
/// @param arguments 参数
/// @param completion 回调
- (void)executeStrategyWithArguments:(NSDictionary *)arguments completion:(void (^)(id result))completion {
    if (self.strategy && [self.strategy respondsToSelector:@selector(arguments:completion:)]) {
        [self.strategy arguments:arguments completion:completion];
    }
}

@end

当然,字典列表对应实现的这几个策略类,ZPFlutterBaseTrackStrategy, ZPFlutterBaseCatcherStrategy, ZPFlutterBaseImageBrowserStrategy, ZPFlutterBaseImagePickerStrategy 都实现了ZPFlutterBaseStrategyProtocol这个协议,所以通过[self.strategy arguments:arguments completion:completion];可以执行到每个类中的方法。

以ZPFlutterBaseCatcherStrategy举例。


@interface ZPFlutterBaseCatcherStrategy : NSObject

@end
@implementation ZPFlutterBaseCatcherStrategy


- (void)arguments:(NSDictionary *)arguments completion:(void (^)(id result))completion {
    if (ZP_F_BASE_IS_DICTIONARY(arguments)) {
        NSString *exceptionInfo = ZP_F_BASE_IS_STRING(arguments[@"info"]) ? arguments[@"info"] : @"";
        NSDictionary *context = ZP_F_BASE_IS_DICTIONARY(arguments[@"context"]) ? arguments[@"context"] : nil;
        if (exceptionInfo.length > 0) {
            Class cls = [[ZhaopinFlutterBaseManager sharedInstance] getClassWithKey:ZP_F_BASE_CARCHER_KEY];
            // 验证是否实现 ZhaopinFlutterBaseProtocol 协议
            if (ZP_F_BASE_IS_PROTOCOL(cls, @protocol(ZPFlutterBaseCatcherProtocol))) {
                [cls catchException:exceptionInfo context: context];
                if (completion) {
                    NSString *jsonString = [ZhaopinFlutterBaseManager formatSuccessResultWithData:@{}];
                    completion(jsonString);
                }
                return;
            }
        }
    }
    if (completion) {
        NSString *jsonString = [ZhaopinFlutterBaseManager formatFailureResultWithData:@{} code:@"-1" errorMsg:@"参数解析失败"];
        completion(jsonString);
    }
}

@end

到目前为止,flutter_base_plugin这个组件库中设计方法已经说完了,那么来考虑下,如何实现插拔,如何解耦。
秉承着注册哪个用哪个,我们同样可以使用字典来解决。
在组件库中,暴露出存取方法。

/// 获取实现类
/// @param key 插件约定 key
- (Class)getClassWithKey:(NSString *)key;

/// 注册实现类
/// @param key 插件约定 key
/// @param baseClass 实现类必须实现对应协议
- (void)registerClassWithKey:(NSString *)key class:(Class) baseClass;

看上面的ZPFlutterBaseCatcherStrategy中的实现
通过getkey方法,拿到别的组件中注册的实现类。

Class cls = [[ZhaopinFlutterBaseManager sharedInstance] getClassWithKey:ZP_F_BASE_CARCHER_KEY];

哪里需要用我们就在哪里注册

        [FlutterBasePlugin registerClassWithKey:ZP_F_BASE_CARCHER_KEY class:NSClassFromString(@"ZPMFlutterExceptionCatcher")];

ZPMFlutterExceptionCatcher这个类,就是最终的实现类。

@interface ZPMFlutterExceptionCatcher : NSObject 
+ (void)catchException:(NSString *)exceptionInfo context:(NSDictionary *)context;
@end
@implementation ZPMFlutterExceptionCatcher
+ (void)catchException:(NSString *)exceptionInfo context:(NSDictionary *)context {
    NSString *crashReason = [NSString stringWithFormat:@"%@", exceptionInfo];

    [Bugly reportException:[[NSException alloc] initWithName:@"FlutterException" reason:crashReason userInfo:@{}]];
}
@end

整个文件的结构如下


image.png

你可能感兴趣的:(『iOS』如何设计一个好的插件(策略模式))