WKWebView Communication Latency Revisited #

Earlier this year I posted about WKWebView communication latency and how it’s not quite as good as UIWebView (when using WKScriptMessageHandler, the officially sanctioned mechanism). There have a been a few developments in this area, so an update seems warranted.

Things appear to be promising for iOS 9: In late June Mark Lam fixed ScriptMessageHandlerDelegate::didPostMessage to reuse JSContext instances across calls. Now that I had an Apple engineer to bug appeal to, I filed a bug asking for same fix to be applied to [WKWebView evaluateJavaScript:completionHandler:], to help with execution round trips. Mark kindly obliged, and the fix was picked up (merged?) for iOS 9 beta 4.

Earlier, in April, Ted Suzman emailed me to ask if I’d considered modifying document.title to send data from the JS side (since it’s mirrored on the native side as the title property on the WKWebView, which can be observed via KVO). I implemented this approach and it seemed to work quite well. Ted also suggested trying location.replace (instead setting location.hash), which (though it should be equivalent) ends up being slightly faster for both UIWebView and WKWebView (implementation).

Ted’s messages got me thinking about other WKWebView properties that could be manipulated from the JS side, and so I took a closer look at the delegate protocols. The webView:runJavaScriptAlertPanelWithMessage:initiatedByFrame:completionHandler: method on WKUIDelegate caught my eye. We don’t use window.alert() in Quip, but this seemed like it would provide a way of getting a string from JS to native with minimal overhead (as previously mentioned, Quip encodes all JS ↔ native communications as strings already, so we don’t want the web view to do any other serialization for us). Better yet webView:runJavaScriptTextInputPanelWithPrompt:defaultText:initiatedByFrame:completionHandler: (which maps to window.prompt()) allows the native side to return a value to the JS side. I implemented these two approaches too.

Here are the results from testing the various communication mechanisms using my test bed. Tests were run on iPad Air 2’s, one running iOS 8.4 and another running iOS 9.0 beta 5

Method/OS iOS 8.4 iOS 9.0 beta 5
location.hash 0.26 0.28
location.replace 0.18 0.18
WKScriptMessageHandler 2.94 0.63
location.hash 0.69 0.58
location.replace 0.46 0.51
document.title 0.57 0.63
window.alert() 0.42 0.46
window.prompt() 0.37 0.45
JS execution round-trip
UIWebView 0.17 0.16
WKWebView 2.60 0.39

Quip is still using UIWebView since we’re still supporting iOS 7 (and supporting both web views did not seem like it would be worth the complexity). However, once iOS 9 is released we will most likely drop iOS 7 support, so it’s good to know that switching to WKWebView will not pose an unreasonable latency burden (though it remains to be seen if the selective swizzling and subview spelunking that we do will carry over).


Interesting how UIWebView still beats WKWebView in every test. The differences are minimal, though, so it's nice to see that the gap is closing.
Matt: WKWebView uses a multi-process architecture, so it will always have some IPC overhead over UIWebView. But yes, it's good to see that the overhead is minimal.
Thanks for the posts on the topic, they are great. I'm looking at implementing an OSX chart app which will involve high latency communication from native to JS (about 40 per sec) to update a chart in canvas in the WKWebView/UIWebView. Your posts seem to address JS to native but do you know how performance matches up in the other direction and how would you suggest to do it. Can we just write data to document.title or something similar?
John: The "JS execution round-trip" numbers should be indicative indicative of what you would see (they indicate native -> JS -> native time, so native -> JS is a strict subset of that).
And how would you recommend sending a value from native to the JS side so the JS knows it needs to respond to this new value?
John: If you're using WKWebView then evaluateJavaScript:completionHandler: handles the full round trip (it gets the return value from JS and passes it to the completionHandler block.
Okay great, one last question; if I'm looking at around 0.4ms on the newer versions for the round trip and I'm executing 40 calls/sec does that mean there could be about 16ms+ of unresponsiveness/inactivity on the UI per second?
John: Part of that time is spent in another process (the one running the WKWebView), so your process is not blocked, and the UI should remain responsive.

Post a Comment