如何为设计一个可以好的插件库
我觉得,一个好的插件库,首先必须不能跟其他组件库耦合,那么该如何设计呢?
我觉得如果要设计好这个东西,必不可少的那就是协议,恰当的来说是使用了策略模式。
下面就用为flutter设计的插件来说一下,应该怎么来做。
flutter与原生之间的交互式用channel来实现的,当然可以不用太在意这些东西,看思路就好。
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)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<ZPFlutterBaseStrategyProtocol> 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 <NSObject>
@required
/// 埋点查找实现类并调用
/// @param arguments flutter 参数
/// @param completion 调用完成回调
- (void)arguments:(NSDictionary *)arguments completion:(void (^)(id result))completion;
@end
@interface ZPFlutterBaseContext()
@property(nonatomic, strong) id<ZPFlutterBaseStrategyProtocol> strategy;
@end
@implementation ZPFlutterBaseContext
/// 初始化
/// @param strategy 策略实现
- (instancetype)initWithStrategy:(id<ZPFlutterBaseStrategyProtocol>) 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<ZPFlutterBaseStrategyProtocol>
@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 <ZPFlutterBaseCatcherProtocol>
+ (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
整个文件的结构如下