tl;DR: only 2/3 bugs have an impact, one has a trivial workaround (jQuery snippet included below), the other one is UX related. Socket.IO needs no updates.
In the past 48 hours a lot has been written about the introduction of iOS6. With such a major software release it’s not surprise that a few issues have been introduced. Of particular importance to us web developers, three different bugs have been attributed to Mobile Safari:
- A continuous “spinning loading indicator” reported on TechCrunch
- Broken long-polling due to a connection limit set to 1 explained by Real Software
- The introduction of aggressive caching of POST requests, reported on Stack Overflow
As it turns out, a lot of misinformation is being spread as well. In addition, all of these bugs are of very high importance to the development of the Socket.IO and Engine.IO realtime frameworks. Therefore I took the time to document the actual bugs and their workarounds in this post.
The spinning bug
This bug report points to the degradation of UX by the introduction of a spinner whenever an AJAX request is active. This is specially concerning for apps that are constantly polling the server (eg: any realtime app).

When I first read reports of this bug, I was skeptical of whether it really had to be attributed to iOS6. The main reason for this is that this behavior already existed, and Socket.IO already has had a workaround for this for a long time. In fact, this bug has also affected many mobile and desktop browsers, which led to our decision of always deferring long polling requests to when the page has finished loaded.
The problem is that this workaround no longer works on iOS6.
Conclusion: as of today, no known workaround is available. Update: from my observations the spinner is very consistently shown for both native and web applications, which means that a workaround might not be feasible at all.
The AJAX cache bug
The gist is that POST AJAX requests are aggressively cached in the absence of cache control headers, contrary to the behavior exhibited by most user agents.
I was able to reproduce this easily:

I’ve documented two workarounds:
- Appending a random identifier or timestamp to the URL
- Sending the
Pragma: no-cacheorCache-control: no-cacheheader from the server
This is a serious bug that impacts a large number of web applications. If you’re in a hurry, you can quickly fix it on the client side, by including a timestamp in the query string. If you are using jQuery, the following snippet should do the trick:
if (/OS 6_/.test(navigator.userAgent)) {
$.ajaxSetup({ cache: false });
}
Conclusion: I strongly recommend you implement one of the two documented workarounds for your application. Ideally you want to set the right header on the server-side to avoid the potential eviction of useful cached objects (thanks Giovanni Spedito for the pointer), and to avoid user agent sniffing. Socket.IO was already timestamping URLs to fix very similar behavior introduced as far back as IE6 days.
The long-polling bug
The report was that with iOS6 “Safari is allowing only one connection at a time”. I have not been able to verify this.
Socket.IO’s acceptance test suite for the long polling XHR transport is exhibiting similar performance to Desktop Chrome:

Conclusion: long-polling continues to work normally with HTTP Keep-Alive sockets and requests that are deferred until page load, the way Socket.IO has always done it.
Update: Steve Souders adds that iOS6 Safari supports up to 5 sockets per hostname, which means it can comfortably support applications that use long-polling.
if ( comments_open() ) { ?>
17 Comments
Including a timestamp in every POST request will work, but it will also pollute the cache with useless responses and in the case of long polling it will probably fill the browser cache in a matter of hours or even shorter, possibly evicting useful contents; please don’t advise that
Thanks for the suggestion. I updated the post accordingly.
I’m not sure if the limit is 1 to any domain at a time. But I too have experienced strange connectivity issues, which indicate a more aggressive connection limit. This needs more investigation.
Let me know if you find out more. I’m curious since I was able to get I believe 4 sockets going without problems.
There is one more bug I haven’t seen anybody writing about. If a touch event causes a setTimeout, but that same touch event causes scrolling, that timer will never fire.
More information and a workaround here: https://gist.github.com/3755461
you call the spinner a UX bug, I call it my device correctly informing me that you’re using my battery with background network traffic.
Pragma header is a bit ugly. Cache-Control: no-cache would be more elegant.
HTTP/1.1 caches SHOULD treat “Pragma: no-cache” as if the client had sent “Cache-Control: no-cache” (RFC 2616 14.32).
This is definitely the case for iOS6 as the testcase shows. Of course,
Cache-Controlis a good option as well.“With such a major software release it’s not surprise that a few issues have been introduced.”
No surprise for Apple customers, maybe. I would have you know that other OS providers will release beta and preview versions of a new OS so the ISV ecosystem can check the compatibility of their apps and find bugs BEFORE the new version is released to the public.
When I inspect my ajax-request in chrome (Network XHR) I see both
Cache-Control:no-cache
and
Pragma:no-cache
Still iOS6-devices seem to cache it…..
Pingback: npcode » iOS6 사파리의 POST 캐싱 버그에 대해
I got this snippet of code and it works perfect.
$.ajaxPrefilter(function (options, originalOptions, jqXHR) {
// you can use originalOptions.type || options.type to restrict specific type of requests
options.data = jQuery.param($.extend(originalOptions.data||{}, {
timeStamp: new Date().getTime()
}));
});
I also was NOT able to reproduce the “1 connection per hostname” bug. This test page loads 6 images. Each image takes 5 seconds to download. If they were downloaded 1 at a time then it would take 30 seconds for window.onload to fire. But I ran this on iOS6 (iPhone 4) in mobile safari, FB app, and Twitter app and it takes 10 seconds to load. When I tail the server logs I see it making 5 requests, then the sixth.
So it appears that iOS 6 actually does FIVE CONNECTIONS PER HOSTNAME.
http://stevesouders.com/cuzillion/?c0=bi1hfff5_0_f&c1=bi1hfff5_0_f&c2=bi1hfff5_0_f&c3=bi1hfff5_0_f&c4=bi1hfff5_0_f&c5=bi1hfff5_0_f
Pingback: Problemas de Apple con Safari en iOS6 - centroreservas.com
For our web app we though we had fixed this aggressive cache issue with Cache-Control: no-load, but there remains a problem with home screen links. It looks like IOS6 has a secondary cache for home screen links such that, although the Safari cache has been cleared and the app works fine now from Safari directly, if the app is accessed via a home screen link it seems to access previously cached data.
Pingback: Revision 89: Mobile, Frameworks, iOS 6 | Working Draft
Pingback: Rounded Corners 353 — Security questions | Labnotes