I'm slowly working on eliminating the Swift 6 concurrency warnings in a large Swift codebase. I recently came across a concurrency error in my code that didn't make sense to me. You can see the original code in asyncLetError
below. It evaluates two JavaScript expressions concurrently (my app does something more than print them, of course).
But when compiling with Swift 6 mode on and complete currency checking, each async let
line generates the following error:
ViewController.swift:9:42 Non-sendable type 'Any' returned by implicitly
asynchronous call to main actor-isolated function cannot cross actor boundary
I guess this is because evaluateJavascript
isn't explicity returning a Sendable
object. I wasn't sure where to go next, so I tried moving away from the async let
approach (see awaitWorksFine
).
That code actually compiles perfectly, but...what's the difference? Why can't I use the async let
approach? I considered filing a bug, but possibly this is expected behavior.
import UIKit
import WebKit
class ViewController: UIViewController {
let webView: WKWebView = WKWebView(frame: .zero)
func asyncLetError() {
Task {
async let urlObject = self.webView.evaluateJavaScript("document.location.href")
async let titleObject = self.webView.evaluateJavaScript("document.title")
print(try await urlObject)
print(try await titleObject)
}
}
func awaitWorksFine() {
Task {
let urlObject = try await self.webView.evaluateJavaScript("document.location.href")
let titleObject = try await self.webView.evaluateJavaScript("document.title")
print(urlObject)
print(titleObject)
}
}
}