原文地址:edgardegas.github.io/dsbridge-sw…

大家好,今日介绍我新写的一个开源库:DSBridge-Swift,它是 DSBridge-iOS 的一个 Swift 翻新版。

DSBridge-iOS 是一个深受大家喜爱的 JavaScript Bridge,尽管现已尘封 6 年,但仍然广为人所用,也有不少新的 Issue。现在它面对一个比较大的问题,那便是 iOS 体系迭代。比方 iOS 16.4 推出的新 API:

@available(iOS 16.4, *)
func webView(
    _ webView: WKWebView,
    willPresentEditMenuWithAnimator animator: any UIEditMenuInteractionAnimating
) {
}

即使你设置了 dsuiDelegate 并且完成了这个办法,在网页选中文本、弹出修正栏的时分,这个办法仍然不会被调用。原因是按照 DSBridge-iOS 的规划,WKUIDelegate 中任何一个办法都有必要先在 DWKWebView 中完成一遍,它才可能转发给你的 dsuiDelgate

由于上述原因,这个库有必要要经过修正本身的源码才干匹配 iOS 体系的更新。这不契合[开闭准则]({% post_url 2024-02-04-再谈SOLID准则 %})。

DSBridge-Swift 选择不站在开发者和 WKWebView 之间。DSBridge-Swift 的 DSBridge.UIDelegate 只做了一件事,便是捕获来自 JS 的调用,而将其他的署理办法悉数转发给开发者自己设置的 WebView.uiDelegate,由开发者自己决定是否完成、怎么完成。

因而也就没有 dsuiDelegate 了,直接设置 uiDelegate 就能够了。

DSBridge-iOS 默认会为你完成 alertconfirmprompt 的弹窗,但现在的局面便是,它所运用的 UIAlertView 现已被 iOS 弃用了。出于和上面相同的原因,DSBridge-Swift 选择由开发者自己完成这些呼应,比方经过设置 uiDelegate,并完成 runJavaScriptConfirmPanelWithMessagerunJavaScriptAlertPanelWithMessagerunJavaScriptTextInputPanelWithPrompt 来弹出弹窗。

一句话归纳便是: DSBridge.WebView 是一个原汁原味的 WKWebView

动态 VS 静态

在原来的 DSBridge-iOS 中,你的 JavaScript Object 有必要是 NSObject 子类,且每个你要露出给 JS 的办法都需求标示 @objc

在新的 DSBridge-Swift 中,你能够用纯 Swift 的类而不需求继承 NSObject

@Exposed
class MyInterface {
    func returnValue() -> Int { 101 }
    @unexposed
    func localMethod()
}

只需求加上 @Exposed 宏就能将你的类型露出给 JS。不想露出的办法则加上 @unexposed 标示即可。

已然咱们现已绕过了动态,那你乃至能够用 structenum 来声明你的 Interface(对,JavaScriptObject 现在改名叫 Interface):

@Exposed
enum EnumInterface {
    case onStreet
    case inSchool
    func getName() -> String {
        switch self {
        case .onStreet:
            "Heisenberg"
        case .inSchool:
            "Walter White"
        }
    }
}

这就声明了一个非常漂亮的一体两面的接口集,供给 getName 接口。

其他比方参数、返回值、回调等,以及如何调用,都和原库相同

基本原理与开闭准则

前面咱们提到了[开闭准则]({% post_url 2024-02-04-再谈SOLID准则 %}),DSBridge-Swift 充沛遵照了开闭准则。

首先 DSBridge-Swift 的 DSBridge.WebView 中几乎没有逻辑,一切逻辑都在作为中枢的拱心石 Keystone 中。

拱心石(英语:Keystone),是砖石门顶上的楔形石头以及圆形石头。这些石块是施工过程中最后一块安放的石头,它首要能将一切的石头固定在方位上。 — 维基百科

解析来自 JS 的调用

你能够修正 KeystonejsonSerializer 和/或 methodResolver

(webView.keystone as! Keystone).jsonSerializer = MyJSONSerializer()
(webView.keystone as! Keystone).methodResolver = MyMethodResolver()

这两个目标担任将来自 JS 的调用转化为 IncomingInvocation(DSBridge-Swift 关于来自 JS 的调用的封装)。

想用 SwiftyJSON 或许 HandyJSON?想修正传参格局?没问题,修正 jsonSerializer 就行。

还有比方 DSBridge-Swift 仅在开发环境中打印 JSON 序列化报错的详情;生产环境中,详细的目标或 JSON 字符串会被替换为*hashed*或许一个空目标。假如你希望改动这一行为,你能够自己界说过错类型,而不运用 DSBridge.JSON 之下的。

Native 调用 JS

Keystone.javaScriptEvaluator 担任办理一切发向 JS 的音讯,模仿 DSBridge-iOS,它每 50ms 才履行一次 JS 脚本,防止履行过于频频,被 iOS “丢包”。原来的 DSBridge-iOS 只针对回调(呼应来自 JS 的异步调用)做了优化Native 主动调用仍然会呈现丢包;DSBridge-Swift 则关于 Native 的主动调用也做了等待队列。

假如你需求做进一步的优化,或许不想要这样的优化,复原本来的体验,你彻底能够将 Keystone.javaScriptEvaluator 替换掉。

派发来自 JS 的调用

Keystone.invocationDispatcher 担任办理一切你注册的 Interface,并担任派发 IncomingInvocation,你能够替换它,供给你自己的完成。

日志

DSBridge-Swift 的大部分日志都是经过 DSBridge.sharedLogger() 打印的,它调用 os_log API,不仅能够从 Xcode 控制台看到打印,也能够在体系的比方 macOS 的控制台流式传输。

为了契合开发者对 DSBridge-iOS 的原本行为的预期,以及为了匹配调用的数据结构,咱们难以抛出、传递和处理过错。包括新增的 Native 调用 JS 的 call(_:with:thatReturns:completion:) API,尽管 completion 返回的是一个 Result<T, any Swift.Error>,但也只是返回调用过程中的过错,Native 和 JS 之间并不能互相认错。

因而 DSBridge 将重度依靠日志。经过替换 DSBridge.sharedLogger() ,你能够供给自己的 DSBridge.ErrorLogging,在测验中把打印出的过错用弹窗展现,或许在生产环境中将日志上签到渠道等。

拱心石

有了上面这样的可扩展性,你乃至能够修正 JS 端的代码,而无需修正 DSBridge-Swift 的源码。

在这之上,你乃至能够从头界说自己的拱心石,彻底替换掉从接收来自 JS 的原始字符串之后的一切逻辑。这需求你完成 DSBridge.KeystoneProtocl,你能够使用或放弃 DSBridge-Swift 中的现成完成,打造一个彻底不同的 Bridge。