前言
其实这两年的更新频次相比21年是降低很多的,主要自22年之后,手上的项目就接连不断,被客户项目整的死去活来,当然在目前这个形式下,有活就说明能继续工作,我应该感到欣慰。
其实,就我自己的认知而言,我觉得自己很难写出非常深奥的技术文章,我也一直为此而感到苦恼与不安,毕竟谁都有一个大神梦,不过我可能只能在此停驻吧。
今天给大家分享一例用enum处理JS传递给Native侧的事件消息
,算是在工作中总结的一点小实践,如果可以给大家一些启发就好了。
JS事件在Native侧的监听
在App开发过程中,我们会使用到H5页面,而加载H5页面,可能就不可避免需要和Web进行交互。
在iOS侧,我们一般都会使用到WebKit中,WebView相关的API进行设置与交互,其核心代码如下:
let config = WKWebViewConfiguration()
config.userContentController.add(WeakScriptMessageDelegate(scriptDelegate: self), name:"showNativeAlert")
let preferences = WKPreferences()
preferences.javaScriptCanOpenWindowsAutomatically = true
config.preferences = preferences
let webView = WKWebView(frame: view.bounds, configuration: config)
在初始化WebView的时候,我们对WebView的配置项里面config.userContentController.add(WeakScriptMessageDelegate(scriptDelegate: self), name:"showNativeAlert")
我们增加了一个叫"showNativeAlert"
的监听事件,也就是说当JS那边调用下面这个方法的时候,我们可以在代理的回调中监听到:
JS侧触发事件
window.webkit.messageHandlers.showNativeAlert.postMessage('来个原生弹窗');
Native侧监听事件
extension MyJueJinController: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
print("方法名:\(message.name)")
print("参数:\(message.body)")
if message.name = "showNativeAlert" {
print("监听了JS给Nativ的showNativeAlert方法,触发对应的逻辑方法")
}
}
}
同时,我们在Controller析构的时候要记得移除"showNativeAlert"
这个句柄:
deinit {
webView.configuration.userContentController.removeScriptMessageHandler(forName: "showNativeAlert")
}
大家可以看到在整个Native侧,我们一直都在使用"showNativeAlert"
这个硬编码字符串,如果我们WebView监听的事件不多,这样写好像也没什么问题,但是如果监听的事件多了,这样写既不优雅,同时持续维护的成本也会特别高。同时,硬编码的字符串也是隐患。
你不得不在func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage)
写更多的if else if
或者switch
:
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
print("方法名:\(message.name)")
print("参数:\(message.body)")
if message.name = "showNativeAlert" {
print("监听了JS给Nativ的showNativeAlert方法,触发对应的逻辑方法")
} else if message.name = "goToNext" {
} else {
}
switch message.name {
case "showNativeAlert":
break
case "goToNext":
break
default:
break
}
}
难道就没有什么好的办法去处理这些JS传递给Native侧的事件消息的句柄吗?
如果你看过Swift:通过Protocol封装统和入参这篇文章,你一定会有想法的。
这里,我先说结论,通过enum来处理JS传递给Native侧的事件消息的句柄。
你们可能会有疑惑,为什么又又又又是enum,且听我娓娓道来。
使用enum的优势
- enum可以很好的处理硬编码字符串
enum ScriptMessageHandlerType: String {
case showNativeAlert
case goToNext
case helloWorld = "hello_world"
}
enum ScriptMessageHandlerType这种”看似“继承String的方式,实际上是给每个枚举值定义了一个rawValue,并且它直接映射的就是其字符串编码,比如这样打印:
print(ScriptMessageHandlerType.showNativeAlert.rawValue)
"showNativeAlert"
同时我们对于枚举值的rawValue也可以自己进行定义与映射,比如上面代码的case helloWorld = "hello_world"
,我们对其打印:
print(ScriptMessageHandlerType.helloWorld.rawValue)
"hello_world"
同时我们也可以轻易的将一个匹配的字符串转成枚举值,比如下面:
let type = ScriptMessageHandlerType(rawValue: "showNativeAlert")
type此时是一个可选类型,因为ScriptMessageHandlerType(rawValue: "")
如果是一个匹配不上的字符串,那么此时type就nil,在实战运用的时候我们需要注意。
- enum可以遵守CaseIterable协议,简单轻易的将所有的枚举事件转为数组
extension ScriptMessageHandlerType: CaseIterable {}
看见上面这个理由,你可能会觉得,转成数组和监听JS事件有什么关系?
记得这段代码吗:
/// 添加监听句柄
config.userContentController.add(WeakScriptMessageDelegate(scriptDelegate: self), name:"showNativeAlert")
/// 移除监听句柄
webView.configuration.userContentController.removeScriptMessageHandler(forName: "showNativeAlert")
如果我们有更多的句柄需求,这段代码你可能就多写几次了,???
不过如果此时我们使用的是enum,那么写起来就不费吹灰之力了:
/// 使用for循序添加句柄
for type in ScriptMessageHandlerType.allCases {
config.userContentController.add(WeakScriptMessageHandlerDelegate(delegate: self), name: type.rawValue)
}
/// 使用for循序移除句柄
for type in ScriptMessageHandlerType.allCases {
webView.configuration.userContentController.removeScriptMessageHandler(forName: type.rawValue)
}
是不是这样看起来,非常简洁了呢?
- enum与switch配合使用,不会遗漏逻辑
最后让我来看看如何处理监听的JS事件,我们重构了func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage)
里面代码:
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
print("方法名:\(message.name)")
print("参数:\(message.body)")
guard let type = ScriptMessageHandlerType(rawValue: message.name) else {
return
}
switch type {
case .showNativeAlert:
break
case .goToNext:
break
case .helloWorld:
break
}
}
因为此时ScriptMessageHandlerType类型是有穷的,所以不必写default这个判断。当你在ScriptMessageHandlerType枚举类型中新增枚举值的时候,如果在switch type
中忘记增加新增枚举值的代码判断,会直接编译报错,从而降低因为新增句柄而忘记处理新增句柄逻辑的情况发生。
所以,基于以上几点原因,我用enum处理JS传递给Native侧的事件消息,各位觉得怎么样?
总结
这篇文章,写了这么多,结果到头来,最终还是在说enum的用法。
讲真的,Swift的enum真的非常强大,强大到有的时候我自己在使用的时候也会感叹居然可以这样?
如果持续做Swift开发,大家不妨这样来反推一下自己代码,这里可不可以试试enum?也许一个好点子就来了。
参考文档
自己写的项目,欢迎大家star⭐️
RxStudy:RxSwift/RxCocoa框架,MVVM模式编写wanandroid客户端。
GetXStudy:使用GetX,重构了Flutter wanandroid客户端。
附上WeakScriptMessageDelegate的相关代码
/// 专用WKScriptMessageHandler的代理层
class WeakScriptMessageDelegate: NSObject {
//MARK: - 属性设置 之前这个属性没有用weak修饰,所以一直持有,无法释放
private weak var scriptDelegate: WKScriptMessageHandler!
//MARK: - 初始化
convenience init(scriptDelegate: WKScriptMessageHandler) {
self.init()
self.scriptDelegate = scriptDelegate
}
}
extension WeakScriptMessageDelegate: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
scriptDelegate.userContentController(userContentController, didReceive: message)
}
}