ref:http://blog.persistent.info/2013/10/a-faster-uiwebview-communication.html

Use location.hash or the click() method on a dummy <a> node (instead of location.href or the src attribute of iframes) to do fast synthetic navigations that trigger aUIWebViewDelegate's webView:shouldStartLoadWithRequest:navigationType: method.

As previously mentionedQuip's editor on iOS is implemented using a UIWebView that wraps a contentEditable area. The editor needs to communicate with the containing native layer for both big (document data in and out) and small (update toolbar state, accept or dismiss auto-corrections, etc.) things. While UIWebView provides an officially sanctioned mechanism for getting data into it (stringByEvaluatingJavaScriptFromString¹), there is no counterpart for getting data out. The most commonly used workaround is to have the JavaScript code trigger a navigation to a synthetic URL that encodes the data, intercept it via the webView:shouldStartLoadWithRequest:navigationType: delegate method and then extract the data out of the request's URL².

The workaround did allow us to communicate back to the native Objective-C code, but it seemed to be higher latency than I would expect, especially on lower-end devices like the iPhone 4 (where it was several milliseconds). I decided to poke around and see what happened between the synthetic URL navigation happening and the delegate method being invoked. Getting a stack from the native side didn't prove helpful, since the delegate method was invoked via NSInvocation with not much else on the stack beyond the event loop scaffolding. However, that did provide a hint that the delegate method was being invoked after some spins of the event loop, which perhaps explained the delays.

On the JavaScript side, we were triggering the navigation by setting the location.href property. By starting at the WebKit implementation of that setter, we end up inDOMWindow::setLocation, which in turn uses NavigationScheduler::scheduleLocationChange³. As the name “scheduler” suggests, this class requests navigations to happen sometime in the future. In the case of explicit location changes, a delay of 0 is used. However, 0 doesn't mean “immediately”: a timer is still installed, and WebKit waits for it to fire. That involves at least one spin of the event loop, which may be a few milliseconds on a low-end device.

I decided to look through the WebKit source to see if there were other JavaScript-accessible ways to trigger navigations that didn't go through NavigationScheduler. Some searching turned up the HTMLAnchorElement::handleClick method, which invoked FrameLoader::urlSelected directly (FrameLoader being the main entrypoint into WebKit's URL loading). In turn, the anchor handleClick method can be directly invoked from the JavaScript side by dispatching a click event (most easily done via the click() method). Thus it seemed like an alternate approach would be to create a dummy link node, set its href attribute to the synthetic URL, and simulate a click on it. More work than just setting the location.href property, but perhaps it would be faster since it would avoid spinning the event loop.

Once I got that all hooked up, I could indeed see that everything was now running slightly faster, and synchronously too — here's a stack trace showing native-to-JS-to-native communication:

 #0: TestBed`-[BenchmarkViewController endIteration:]
#1: TestBed`-[BenchmarkViewController webView:shouldStartLoadWithRequest:navigationType:]
#2: UIKit`-[UIWebView webView:decidePolicyForNavigationAction:request:frame:decisionListener:]
...
#17: WebCore`WebCore::FrameLoader::urlSelected(...)
...
#23: WebCore`WebCore::jsHTMLElementPrototypeFunctionClick(...)
#24: 0x15e8990f
#25: JavaScriptCore`JSC::Interpreter::execute(...)
...
#35: UIKit`-[UIWebView stringByEvaluatingJavaScriptFromString:]
#36: TestBed`-[BenchmarkViewController startIteration]
...

More recently, I took a more systematic approach in evaluating this and other communication mechanisms. I created a simple test bed and gathered timings (measured in milliseconds) from a few devices (all running iOS 7):

Method/Device iPhone 4
A4
iPad Mini
A5
iPhone 5
A6
iPhone 5s
A7
Simulator
2.7 GHz Core i7
location.href 3.88 2.01 1.31 0.84 0.22
location.hash 1.42 0.86 0.55 0.39 0.13
<a> click 1.50 0.87 0.58 0.40 0.13
frame.src 3.52 1.86 1.16 0.87 0.29
XHR sync 8.66 3.25 2.19 1.34 0.45
XHR async 6.38 2.32 1.62 1.00 0.33
document.cookie 2.89 1.22 0.78 0.55 0.16
JavaScriptCore 0.33 0.18 0.14 0.09 0.03

The mechanisms are as follows:

  • location.href: Setting the location.href property to a synthetic URL.
  • location.hash: Setting the location.hash property to a the data encoded as a fragment. The reason why it's faster than replacing the whole URL is because same-page navigations are executed immediately instead of being scheduled (thanks to Will Kiefer for telling me about this).
  • <a> click: Simulating clicking on an anchor node that has the synthetic URL set as its href attribute.
  • frame.src: Setting the src property of a newly-created iframe. Based on examining the chrome.js file inside the Chrome for iOS .ipa, this is the approach that it uses to communicate: it creates an iframe with a chromeInvoke://... src and appends it to the body (and immediately removes it). This approach does also trigger the navigation synchronously, but since it modifies the DOM the layout is invalidated, so repeated invocations end up being slower.
  • XHR sync/async: XMLHttpRequests that load a synthetic URL, either synchronously or asynchronously; on the native side, the load is intercepted via a NSURLProtocol subclass. This is the approach that Apache Cordova/PhoneGap prefers: it sends an XMLHttpRequest that is intercepted via CDVURLProtocol.This also ends up being slower because the NSURLProtocolmethods are invoked on a separate thread and it has to jump back to the main thread to invoke the endpoint methods.
  • document.cookie: Having the JavaScript side set a cookie and then being notified of that change via NSHTTPCookieManagerCookiesChangedNotification. I'm not aware of anyone using this approach, but the idea came to me when I thought to look for other properties (besides the URL) that change in a web view which could be observed on the native side. Unfortunately the notification is triggered asynchronously, which explains why it's still not as fast as the simulated click.
  • JavaScriptCore: Direct communication via a JSContext using Nick Hodapp's mechanim. Note that this approach involves adding a category on NSObject to implement aWebFrameLoadDelegate protocol method that is not present on iOS. Though the approach degrades gracefully (if Apple ever provides an implementation for that method, their implementation will be used), it still relies on enough internals and "private" APIs that it doesn't seem like a good idea to ship an app that uses it. This result is only presented to show the performance possibilities if a more direct mechanism were officially exposed.

My tests show the location.hash and synthetic click approaches consistently beating location.href (and all other mechanisms), and on low-end devices they're more than twice as fast. One might think that a few milliseconds would not matter, but when responding to user input and doing several such calls, the savings can add up⁴.

Quip has been using the synthetic click mechanism since before launch, and so far with no ill effects. There a few things to keep in mind though:

  • Repeatedly setting location.href in the same spin of the event loop results in only the final navigation happening. Since the synthetic clicks are processed immediately, they will all result in the delegate being invoked. This is generally desirable, but you may have to check for reentrancy since stringByEvaluatingJavaScriptFromString is synchronous too.
  • Changing the href attribute of the anchor node would normally invalidate the layout of the page. However, you can avoid this by not adding the node to the document when creating it.
  • Not having the anchor node in the document also avoids triggering any global click event handlers that you have have registered.

I was very excited when iOS 7 shipped public access to the JavaScriptCore framework. My hope was that this would finally allow something like Android's @JavaScriptInterfacemechanism, which allows easy exposure of arbitrary native methods to JavaScript. However, JavaScriptCore is only usable in standalone JavaScript VMs; it cannot (officially⁵) be pointed at a UIWebView's. Thus it looks like we'll be stuck with hacks such as this one for another year.

Update on 1/12/2014: Thanks to a pull request from Felix Raab, the post was updated showing the performance of direct communication via JavaScriptCore.

  1. I used to think that this method name was preposterously long. Now that I've been exposed to Objective-C (and Apple's style) more, I find it perfectly reasonable (and the names that I choose for my own code have also gotten longer too). Relatedly, I do like Apple's consistency for will* vs. did* in delegate method names, and I've started to adopt that for JavaScript too.
  2. There are also more exotic approaches possible, for example LinkedIn experimented with WebSockets and is (was?) using a local HTTP server.
  3. Somewhat coincidentally, NavigationScheduler is where I made one of my first WebKit contributions. Though back then it was known as RedirectScheduler.
  4. As Brad Fitzpatrick pointed out on Google+, a millisecond is still a long time for what is effectively two function calls. The most overhead appears to come from evaluating the JS snippet passed to stringByEvaluatingJavaScriptFromString, followed by constructing the HTTP request that is passed to webView:shouldStartLoadWithRequest:navigationType:.
  5. In addition to implementing a WebFrameLoadDelegate method, another way of getting at a JSContext is via KVO to look up a private property. The latter is in some ways more direct and straightforward, but it seems even more likely to run afoul of App Store review guidelines.

[转]A Faster UIWebView Communication Mechanism的更多相关文章

  1. Linux Communication Mechanism Summarize

    目录 . Linux通信机制分类简介 . 控制机制 0x1: 竞态条件 0x2: 临界区 . Inter-Process Communication (IPC) mechanisms: 进程间通信机制 ...

  2. [Angular] Implementing A General Communication Mechanism For Directive Interaction

    We have modal implement and now we want to implement close functionality. Becuase we use a structure ...

  3. NetLink Communication Mechanism And Netlink Sourcecode Analysis

    catalog . Netlink简介 . Netlink Function API Howto . Generic Netlink HOWTO kernel API . RFC Linux Netl ...

  4. Communication between C++ and Javascript in Qt WebEngine(转载)

    Communication between C++ and Javascript in Qt WebEngine admin January 31, 2018 0 As Qt WebKit is re ...

  5. ASP.NET Core – 2300% More Requests Served Per Second

    http://www.ageofascent.com/asp-net-core-exeeds-1-15-million-requests-12-6-gbps/ ASP.NET Core – Excee ...

  6. The Go Programming Language. Notes.

    Contents Tutorial Hello, World Command-Line Arguments Finding Duplicate Lines A Web Server Loose End ...

  7. Game Engine Architecture 5

    [Game Engine Architecture 5] 1.Memory Ordering Semantics These mysterious and vexing problems can on ...

  8. What can Reactive Streams offer EE4J?

    https://developer.lightbend.com/blog/2018-02-06-reactive-streams-ee4j/index.html By James Roper (@jr ...

  9. 转 What is Redis and what do I use it for?

    原文: http://stackoverflow.com/questions/7888880/what-is-redis-and-what-do-i-use-it-for Redis = Remote ...

随机推荐

  1. Unity该插件NGUI学习(1)—— 环境结构

    Unity官方网站http://unity3d.com/unity/download下载最新版本4.5.4 发现在神圣的论坛裂纹(Windows)版本号http://game.ceeger.com/f ...

  2. NFC学习笔记2——Libnfc简介及安装

    我一直希望自己的文章做一些记录的英文翻译.趁着学习NFC,现在,libnfc主页libnfc介绍和不同的操作系统libnfc文章做一些翻译安装.一方面,提高自己的英语,一方面有了解libnfc. 原文 ...

  3. crawler_java_数据平台结构

    大数据生态架构

  4. oracle_powerdesinger逆向工程 , PDM 文件 注释到name的完美解决方案 comment2name

    1. 从oracle 到 PDM文件  逆向工程中 ,需要注意 去掉“” ,这个百度下很多帖子,用于去掉引号 2. 从注释copy到name运行脚本会有个问题就是 ,有些注释太长,不美观 解决方案, ...

  5. 大约HR升级版的设计为组汇总

    该公司刚刚完成HR系统升级,系统从单一公司实现使用更多的公司使用变更.在一个月的时间升级,虽然很苦,但他们自己的系统架构的感觉获益,有以下的详细的见解: 一.MVC还是非常重要 系统框架是五年前用de ...

  6. 2440裸 Delay(); 和 while(!(rUTRSTAT0 &amp; 0x2)); 问题

    前两天写RTC中断 使用串行输出 它发现,该方案将while(!(rUTRSTAT0 & 0x2));走不出的情况.的 解决方法: main函数添加: U32 mpll_val = 0,con ...

  7. 内置在虚拟机上64位操作系统:该主机支持 Intel VT-x,但 Intel VT-x 残

    VT-Virtual Technology. 在这里特别说一下:虚拟64位操作系统,须要特别在BIOS中打开VT支持.怎样提示:此主机不支持Intel VT-x,则不可能虚拟出64位系统. 当提示:此 ...

  8. 欧拉计划&#183;Q8

    题目8:找出这个1000位数字中连续13个数字乘积的最大值. 找出下面这个1000位的整数中连续13个数字的最大乘积. 7316717653133062491922511967442657474235 ...

  9. 【百度地图API】如何制作孪生姐妹地图?

    原文:[百度地图API]如何制作孪生姐妹地图? 任务描述: 我想要两张一模一样的地图!我想要双子地图!我想要孪生姐妹地图! 好好好,统统满足大家! 在这里我不需要使用百度地图API提供的地图缩略图控件 ...

  10. JavaScript中,关于new的那些事

    这篇文章是自己对new学习过程中的一些理解,有不对的地方希望指出,接受组织的批评教育. 导火线,前段时间学习jQuery的时候,看到源码中有这样一段: jQuery = function(select ...