Search code examples
swiftasync-awaitwkwebview

WKScriptMessageHandlerWithReply delegate methods are called on non-main thread


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?


Solution

  • 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")
        }
    }