每当加载新页面时,我想检查新的网址,然后从页面中提取一些信息。这是基本设置:
struct MyWebView: UIViewRepresentable {
@State var someInfo: String?
func makeUIView(context: Context) -> WKWebView {
return WKWebView()
}
func updateUIView(_ uiView: WKWebView, context: Context) { }
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, WKNavigationDelegate {
var parent: MyWebView
init(_ parent: MyWebView) {
self.parent = parent
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
// do something here (see below)
}
}
}
我将“严格并发检查”设置为“完成”,但我不知道如何消除
func webView(...)
中的警告。这是我尝试过的两个选项。
@MainActor
// v Main actor-isolated instance method 'webView(_:didFinish:)' cannot
// be used to satisfy nonisolated protocol requirement
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
let url = webView.url
if url?.path.starts(with: "https://stackoverflow.com") != true {
return
}
webView.evaluateJavaScript("document.body.innerHTML") { (result, error) in
if let someInfo = result as? String {
self.parent.someInfo = someInfo
}
}
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
Task {
let url = await webView.url
if url?.path.starts(with: "https://stackoverflow.com") != true {
return
}
// v Consider using asynchronous alternative function
// v Passing argument of non-sendable type '(Any?, (any Error)?) -> Void'
// into main actor-isolated context may introduce data races
await webView.evaluateJavaScript("document.body.innerHTML") { (result, error) in
if let someInfo = result as? String {
// v Capture of 'self' with non-sendable type
// 'MyWebView.Coordinator' in a `@Sendable` closure
self.parent.someInfo = someInfo
}
}
}
}
不确定是否有人会遇到同样的问题,因为发生了多种事情,但这就是我到目前为止解决它的方法(基于选项 2):
我不是将状态存储在某处,而是存储一个
Task
,然后从父级内部获取值。这解决了Capture of 'self' with non-sendable type 'MyWebView.Coordinator' in a '@Sendable' closure
-警告:
struct MyWebView: UIViewRepresentable {
@Binding var getSomeInfoTask: Task<String?, Never>?
// ...
}
在父母的某个地方:
SomeView()
.task(id: getSomeInfoTask) {
if let task = getSomeInfoTask {
let someInfo = await task.value
// ...
}
}
另外两个警告
Consider using asynchronous alternative function
和 Passing argument of non-sendable type '(Any?, (any Error)?) -> Void' into main actor-isolated context may introduce data races
正在通过使用此扩展来解决。它将完成处理程序转换为 async
函数:
extension WKWebView {
func evaluateJavaScriptAsync<T: Sendable>(_ javaScriptString: String) async throws -> T {
return try await withCheckedThrowingContinuation { continuation in
self.evaluateJavaScript(javaScriptString) { result, error in
if let error = error {
continuation.resume(throwing: error)
} else if let result = result as? T {
continuation.resume(returning: result)
} else {
continuation.resume(throwing: NSError(
domain: "WKWebView",
code: 0,
userInfo: [NSLocalizedDescriptionKey: "JavaScript evaluation returned a non-object result"]
))
}
}
}
}
}
最后调用它看起来像这样:
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
parent.getSomeInfoTask?.cancel()
parent.getSomeInfoTask = Task {
let url = await webView.url
if url?.path.starts(with: "https://stackoverflow.com") != true {
return nil as String?
}
do {
return try await webView.evaluateJavaScriptAsync("document.body.innerHTML")
} catch {
print("Failed loading js: \(error)")
}
return nil as String?
}
}