I'm using WKScriptMessageHandlerWithReply
to receive messages from a WKWebView
.
I recently refactored the code to separate view related code and logic related code into a WebViewController
/WebController
.
class WebController: NSObject {
func performSomeAction() async -> (Any?, String?) {
// ...
}
}
extension WebController: WKScriptMessageHandlerWithReply {
func userContentController(_: WKUserContentController, didReceive message: WKScriptMessage) async -> (Any?, String?) {
guard message.name == MESSAGE_HANDLER_NAME else {
return
}
if message.body == "some_event_name" {
return async performSomeAction()
}
return (nil, "unknown event")
}
}
Since this refactor the method is still called just fine, but no longer on the main thread. But the properties of WKScriptMessage
have to be accessed only the main thread:
Main Thread Checker: UI API called on a background thread: -[WKScriptMessage name]
Why did this behavior change? And what's a good way to work around this?
The delegate methods of WKScriptMessageHandlerWithReply
are called on the main thread by default when the receiver is a subclass of UIResponder
, which also included UIViewController
.
But with the new setup the WebController
is a subclass of NSObject
which results in the delegate method being called on random threads.
To ensure the delegate method is called on the main thread, you can annotate the method with @MainActor
.
extension WebController: WKScriptMessageHandlerWithReply {
@MainActor
func userContentController(_: WKUserContentController, didReceive message: WKScriptMessage) async -> (Any?, String?) {
guard message.name == MESSAGE_HANDLER_NAME else {
return
}
if message.body == "some_event_name" {
return async performSomeAction()
}
return (nil, "unknown event")
}
}