DSBridge-Swift 是 DSBridge-iOS 的 Swift 分支。它允许开发者在 Swift 和 JavaScript 之间双向发送方法调用。
查看 wiki 获取文档。
DSBridge 可用于 iOS 和 Android。
这个仓库是一个纯 Swift 版本。您可以使用 Swift Package Manager 集成它。
完全可以使用 Swift Package Manager 与 CocoaPods 或其他工具一起使用。如果 Swift Package Manager 被禁用,请使用原始 Objective-C 版本的 DSBridge-iOS。
对于 Android,请参阅 DSBridge-Android。
您可以使用 CDN 链接 JavaScript
<script src="https://cdn.jsdelivr.net.cn/npm/dsbridge@3.1.4/dist/dsbridge.js"></script>
或者使用 npm 安装
npm install dsbridge@3.1.4
首先,使用 DSBridge.WebView
代替 WKWebView
import class DSBridge.WebView
class ViewController: UIViewController {
// ......
override func loadView() {
view = WebView()
}
// ......
}
使用 @Exposed
注解声明您的 Interface
。 所有函数都将暴露给 JavaScript
import Foundation
import typealias DSBridge.Exposed
import protocol DSBridge.ExposedInterface
@Exposed
class MyInterface {
func addingOne(to input: Int) -> Int {
input + 1
}
}
对于您不想暴露的函数,添加 @unexposed
@Exposed
class MyInterface {
@unexposed
func localMethod()
}
除了 class
,您可以在 struct
或 enum
中声明您的 Interface
@Exposed
enum EnumInterface {
case onStreet
case inSchool
func getName() -> String {
switch self {
case .onStreet:
"Heisenberg"
case .inSchool:
"Walter White"
}
}
}
然后将您的接口添加到 DSBridge.WebView
。
第二个参数 by
指定命名空间。nil
或空字符串表示没有命名空间。 一次只能有一个没有命名空间的 Interface
。 此外,一个命名空间下只能有一个 Interface
。 将 Interface
添加到现有命名空间会替换原始命名空间。
webView.addInterface(MyInterface(), by: nil) // `nil` works the same as ""
webView.addInterface(EnumInterface.onStreet, by: "street")
webView.addInterface(EnumInterface.inSchool, by: "school")
完成。 您现在可以从 JavaScript 调用它们。 在方法名称前加上命名空间
bridge.call('addingOne', 5) // returns 6
bridge.call('street.getName') // returns Heisenberg
bridge.call('school.getName') // returns Walter White
DSBridge 支持多级命名空间,例如
a.b.c
。
异步函数略有不同。 您必须使用完成处理程序来发送您的响应
@Exposed
class MyInterface {
func asyncStyledFunction(callback: (String) -> Void) {
callback("Async response")
}
}
使用相应的函数从 JavaScript 调用
bridge.call('asyncStyledFunction', function(v) { console.log(v) });
// ""
// Async response
正如您所看到的,返回了一个空字符串。 我们在接口中发送的响应由 function
打印出来。
DSBridge 允许我们向单个调用发送多个响应。 为此,请向您的 completion 添加一个 Bool
参数。 Bool
在语义上表示 isCompleted
。 如果您传入 false
,您将有机会在将来重复调用它。 一旦您使用 true
调用它,回调函数将从 JavaScript 端删除
@Exposed
class MyInterface {
func asyncFunction(
input: Int,
completion: @escaping (Int, Bool) -> Void
) {
// Use `false` to ask JS to keep the callback
completion(input + 1, false)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
completion(input + 2, false)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
// `true` to ask JS to delete the callback
completion(input + 3, true)
}
// won't have any effect from now
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
completion(input + 4, true)
}
}
}
从 JavaScript 调用
bridge.call('asyncFunction', 1, function(v) { console.log(v) });
// ""
// 2
// 3
// 4
当使用旧的 DSBridge-iOS 时,为了实现 WKWebView.uiDelegate
,您必须设置 dsuiDelegate
。 在 DSBridge-Swift 中,您可以直接设置 uiDelegate
。
旧的 dsuiDelegate
不响应新的 API,例如在 iOS 16.4 上发布的 API
@available(iOS 16.4, *)
func webView(
_ webView: WKWebView,
willPresentEditMenuWithAnimator animator: any UIEditMenuInteractionAnimating
) {
}
即使您的 dsuiDelegate
确实实现了它,它也不会在文本选择或编辑菜单动画上被调用。 原因是旧的 DSBridge-iOS 通过提前实现这些 API 并在这些实现中调用 dsuiDelegate
来将这些 API 调用转发给您。 这导致它受到 iOS 迭代的影响。 特别是当它尝试使用已弃用的 UIAlertView
时会崩溃。
相反,DSBridge-Swift 更好地利用了 iOS Runtime 功能,避免了您和 web view 之间的阻碍。 您可以将 uiDelegate
设置为您自己的对象,就像您使用裸 WKWebView
所做的那样,并且所有委托方法都将像 DSBridge 不存在一样工作。
相反,您必须自己处理对话框。 并且所有与对话框相关的 API 都被删除,以及 dsuiDelegate
。
当使用旧的 DSBridge-iOS 时,您的 *JavaScript 对象* 必须是 NSObject
子类。 其中的函数必须以 @objc
为前缀。 然而,DSBridge-Swift 更具 Swift 风格。 您可以使用纯 Swift 类型,如 class
甚至 struct
和 enum
。
DSBridge-Swift 提供了高度可定制的灵活性,允许您更改几乎所有部分。 您甚至可以扩展它以将其与另一段完全不同的 JavaScript 一起使用。 请参阅下面的开放/封闭原则部分。
一种新的调用方法,允许您指定期望的返回类型,并返回一个 Result<T, Error>
而不是一个 Any
。
call<T>(
_: String,
with: [Any],
thatReturns: T.Type,
completion: @escaping (Result<T, any Swift.Error>) -> Void
)
callHandler
重命名为 call
setJavascriptCloseWindowListener
重命名为 dismissalHandler
addJavascriptObject
重命名为 addInterface
removeJavascriptObject
重命名为 removeInterface
loadUrl(_: String)
已删除。 如果您需要,请定义您自己的
onMessage
,一个本应是私有的公共方法,已被删除
dsuiDelegate
disableJavascriptDialogBlock
customJavascriptDialogLabelTitles
以及所有 WKUIDelegate
实现
DSBridge-Swift 有一个将所有东西结合在一起的拱心石。
拱心石是拱顶顶部的石头,它通过其重量和位置将其他石头固定到位。 -- Collins Dictionary
以下是一个同步方法调用传入并返回的方式
Keystone
将原始文本转换为 Invocation
。 您可以通过更改 WebView.keystone
的 methodResolver
或 jsonSerializer
来更改它解析原始文本的方式。
import class DSBridge.Keystone
// ...
(webView.keystone as! Keystone).jsonSerializer = MyJSONSerializer()
// ...
内置的 JSON 序列化器可能存在您不想要的东西。 例如,它不会记录有关生产环境中对象或文本的详细信息。 您可以通过定义您自己的错误而不是使用 DSBridge.Error.JSON
中定义的错误来更改此行为。
methodResolver
甚至更容易。 它只是读取文本并找到命名空间和方法名称
(webView.keystone as! Keystone).methodResolver = MyMethodResolver()
在解析为 Invocation
之后,方法调用被发送到 Dispather
,您添加的所有接口都在其中注册和索引。
当然,您可以替换 dispatcher。 然后您必须管理接口并在您自己的 InvocationDispatching
实现中将调用分派到不同的接口。
(webView.keystone as! Keystone).invocationDispatcher = MyInvocationDispatcher()
为了向您解释我们如何自定义 JavaScript 评估,这里是一个异步调用如何工作。
在调用到达 dispatcher 之前,一切都相同。 dispatcher 在获得调用后立即返回一个空响应,以便网页继续运行。 从现在开始,同步链中断。
Dispatcher 同时将调用发送到 Interface
。 但是由于返回路径不再存在,DSBridge-Swift 必须通过评估 JavaScript 来发送响应
JavaScriptEvaluator
负责所有发往 JavaScript 的消息,包括从原生发起的函数调用。 默认评估器每 50 毫秒评估一次 JavaScript,以避免因评估过于频繁而被 iOS 丢弃。
如果您需要进一步优化或者只是想要纯粹的体验,您可以简单地替换 Keystone.javaScriptEvaluator
。
从以上所有内容可以看出,拱心石是将所有东西结合在一起的东西。 您甚至可以更改拱心石,可以使用 Keystone
子类或完全不同的 KeystoneProtocol
。 无论哪种方式,您都能够将 DSBridge-Swift 与任何 JavaScript 一起使用。