iOS 从WebView来看插件化设计思路

1,473 阅读4分钟

前言

在iOS开发过程中,随着项目的越来越庞大,一些基础组件需要单独剥离出来。单独剥离出来的基础组件也会因为业务需求的不断变化而越来越臃肿难以维护。如何去解决这个问题,插件化的设计可以为大家提供一些思路。接下来我将以webView的插件设计为例。

在成熟的项目中往往会采用封装好一个webView,复用于各个需要的地方。好处自然不言而喻,减少了冗余代码,统一规范,易于维护等。

同时,webView往往需要和H5页面进行交互,交互的实现方式多种多样,在此不表。随着项目规模越来越大,针对不同的业务需求,往往需要在webView中添加各种各样的交互逻辑,最后就导致webView越来越臃肿越来越难以维护。

为此,设计一套插件化的实现,来解决这个问题。 通过插件来赋予webView各种各样的能力,同时插件与webView解耦,结构上更易于维护和易懂。

思路

这里以webView和H5的交互为例。创造出来的每个插件都对应一个Action。在H5将事件抛给webView的时候,webView将事件交给对应的插件去完成。

在此流程上,有两个需要关注的点。

  • webView如何将Action传递给对应实现的Plugin。
  • 对应的Plugin应该怎么实现。

插件的设计

到此,我们需要分析插件的设计要求。

  1. 插件需要有执行Action的能力。
  2. 插件要能够快速被找到。
  3. 插件生命周期要科学。减少资源的消耗。
  4. 插件的实现尽量简单简洁,方便推广使用。

对此,我们可以准备一个插件基类:webViewPlugin

/* 插件查询表 */
static NSMutableDictionary<NSString *, NSString *> *pluginsMapping;
+ (NSString *)actionName {
    return @"";
}
+ (void)registerPlugin {
    
    if (pluginsMapping == nil) {
        pluginsMapping = [NSMutableDictionary<NSString *, NSString *> dictionary];
    }
    [pluginsMapping setObject:NSStringFromClass(self) forKey:[self actionName]];
}
- (nullable __kindof instancetype)initWithHandlerName:(NSString *)handlerName
                                           parameters:(NSDictionary<NSString *, id> *)parameters
                                           forWebView:(webView *)webView {
    
    NSString *className = [pluginsMapping objectForKey:handlerName];
    Class clazz = NSClassFromString(className);
    if (clazz == nil || ![clazz isSubclassOfClass:WebViewPlugin.class]) {
        return nil;
    }
    
    WebViewPlugin *plugin = [[clazz alloc] init];
    plugin.handlerName = handlerName;
    plugin.parameters = parameters;
    plugin.webView = webView;
    return plugin;
}
- (void)startAction {
}

基类中准备了4个方法:

一个是对应的Action的名称,其实也可以理解为每个Plugin对应的key,在webView接收到事件以后,我们需要通过这个Key来查找对应的Plugin。

第二个是注册方法,在这里可以看到我准备了一个静态的Dictionary,使用静态的Dictionary是因为这个字典可以单独存储在静态区,方便随取随用。

第三个就是对应的初始化方法。初始化方法中主要做的事情就是从存储好的注册表里根据外部传进来的key将对应的plugin初始化。

第四个就是plugin需要实现的Action。

到此Plugin的基类就完成了。通过继承并实现对应的方法来添加Plugin。

WebViewPluginClose,那它的实现就是:

+ (void)load {
    [self registerPlugin];
}

+ (NSString *)actionName {
    return @"close_webview";
}

- (void)startAction {
    [self.webView removeFromSuperview];
    
}

在子类的实现中,我将插件的注册方法写在了load方法中。 load方法是项目代码加载以后每个类都会调用的方法,并且父类先于子类。 所以在此添加注册方法免去了上层业务手动注册管理等操作。 同时实现了actionName也就是定义了对应的key,最后将操作放在了startAction中。

webView事件的分发

在webView接收到事件后,根据对应的Key去分发给相应的插件:


WebViewPlugin *plugin = [[WebViewPlugin alloc] initWithHandlerName:name parameters:params forWebView:self];

[plugin startAction];

扩展总结

在此例子的基础上还可以根据业务的需求完善更多的设计。比如将消息数据统一分发给一个PluginManager,在PluginManager中又可以做消息转发数据预处理等等满足更复杂的场景需求。每个插件也可以根据需要赋予不同的能力,甚至规划更多类型的插件。

在此笔者借助webView的例子想向和大家分享的是插件化的这一设计思路。这样的设计思路不仅仅适用于webView等,同样也可以给UIView的组件或某个Controller等使用。在实现业务解藕的同时也让代码更加易于维护。