Wkwebview 组件 封装 开闭原则

2019-03-24  本文已影响0人  jianshudxw

前言

公司以前已经对 WKWebView 进行了封装,但当时的需求很少,主要是展示网页和右上角分享等几个功能。当时主要是通过拦截 url的方式实现。

但现在的功能需求增多,比如选择图片,上次传图片,发布文章等。。。

一般我们用一个类比如 WebBrowerController 开打开所有的网页,本地的或服务器。 这里我用 swift 写代码。 详细代码见 用代码一步一步实现自己的 ios 架构 Hybird 目录

以前的方式缺点

本文侧重封装,解决上面的缺点,所以不详细介绍 js ios 互调的知识点,网络上已经很多。我会以后在代码里将互调的功能补充。我使用Wkwebview 新的JS、 iOS互调的方式,和 新的架构依据 开闭原则 代码容易扩展。

目标

封装一个易于维护和扩展的 Wkwebview。

不易扩展的要代码

//注册 js
 web_.configuration.userContentController.add(self, name: "func1")
 web_.configuration.userContentController.add(self, name: "func2")
 web_.configuration.userContentController.add(self, name: "func3")
// js 回调处理
  if message.name == func1 {
     } else if message.name == func2  {
} else if message.name == func3
...
...
...

这样当功能增多时,这个文件变大 功能混乱, 并且要不停修改无法封装成底层库。

分析

分析耦合,主要是 注册JS和处理JS,将她们分离出去即可。

我们可以给 WebBrowerController 添加代理,由外部不同的代理去实现,不同的代理实现不同方法,由服务器告知需要调用哪些代理

服务器告知需要哪些代理

两种方案:

  1. 解析url
    在 调用 WebBrowerController 时 在url 中约定 详细代码见具体代码。
    主要代码:
    init(url: String) {
        super.init(nibName:nil, bundle:nil)
        guard let components = URLComponents(string: url) else {
            assertionFailure("malformed url: \(url)")
            return
        }
        self.url = URL(string: url)
//        DispatchQueue.main.asyncAfter(deadline: .now() + 3.5) {
//            self.config(self.parseConfig(components.queryItems))
//        }
       config(parseConfig(components.queryItems))
    }
    func parseConfig(_ queryPairs: [URLQueryItem]?) -> Dictionary<String,Array<String>>? {
        var dic: Dictionary<String,Array<String>>? = nil
        _ = queryPairs?.map {
            if $0.name == webconfig, let value = $0.value {
                if  let data = Data(base64Encoded: value) {
                    dic = (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)) as? Dictionary<String,Array<String>>
                }
            }
        }
        return dic
    }
  1. WkWebview js 注册
    主要代码:
  lazy var web: WKWebView = {
        let web_ = WKWebView()
        web_.configuration.userContentController.add(self, name: webconfig) //url注册和这个注册 二选一
        return web_
    }()
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
    if message.name == webconfig {
        config(message.body as? Dictionary<String,Array<String>>)
    }
}

ios 调用对应的 代理

主要代码

    func config(_ serverDic: Dictionary<String,Array<String>>?) {
        if let modules = serverDic?["module"] {
            _ = modules.map { module in
                let namespace = Bundle.main.infoDictionary!["CFBundleExecutable"]as! String
                let clsName = namespace + "." + module
                let cls = NSClassFromString(clsName) as! FeakModule.Type
                let instance = cls.init()
                instance.web = web
                instance.configWebView(web.configuration)
            }
        }
    }

这里需要说明一下:let cls = NSClassFromString(clsName) as! FeakModule.Type 我发现 swift 只能根据类名反射到类,无法根据 协议名 反射到 遵守协议的 类。 这里我使用了一个 FeakModule 其实相对于一个 抽象基类。

protocol WebBrowerDelegate {
    func configWebView(_ config: WKWebViewConfiguration);
}

class FeakModule: UIViewController, WebBrowerDelegate {
    var web: WKWebView!
    func configWebView(_ config: WKWebViewConfiguration) {
    }
}

再定义具体的代理子类如:

class Module1: FeakModule, WKScriptMessageHandler
class Module2: FeakModule, WKScriptMessageHandler

这样 就可以编译成功,当运行时,会生成具体的 Module1 或者 Module2

具体的子类 去做 js 注册 和 处理回调

class Module2: FeakModule, WKScriptMessageHandler {
    override func configWebView(_ config: WKWebViewConfiguration) {
        print("module2 config webview")
        config.userContentController.add(self, name: "module2_func1")
    }
    
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if message.name == "module2_func1" {
            print("js call ios module2_func1")
        }
    }
}

每增加一个新需求,就新建一个 子类。 代码清晰简洁了!

结果

如此处理后,WebBrowerController 就说一个基础组件,可以在里面做一些基础的公共的工作。以后不管增加什么功能,WebBrowerController 都不需要改变。做到了 开闭原则

上一篇 下一篇

猜你喜欢

热点阅读