My apps…

Space Harvest Begin

All-Seeing Interactive is a tiny web design and software company based in London, UK.

Wednesday 6 January 2010

ASIHTTPRequest 1.5

There's a new version of ASIHTTPRequest available now. Grab a copy here. Read on for some notes on the major new features, and details on some of the key API changes.

Performance

Recently, some users have highlighted the performance gap between NSURLConnection and ASIHTTPRequest. Mikhail Kalugin took the time to create some benchmarks to demonstrate the problem. As you can see from his stats, ASIHTTPRequest was significantly slower than NSURLConnection.

ASIHTTPRequest 1.5 includes some changes that should improve performance, fairly drastically in some cases.

ASIHTTPRequest is now a concurrent NSOperation

This concept might be slightly confusing, 'concurrent' suggests being able to run several requests at once, and this has always been possible with ASIHTTPRequest. This is the way it works:

Standard NSOperations are designed to run synchronously - you add them to a queue, it creates a thread for them to run in(i), they run, and when their 'main' method exits, they stop. In the old version of ASIHTTPRequest, requests used a loop to prevent the main function exiting while they waited for the request to finish. Every cycle of the loop, it would update the progress delegates, and check to see if it needed to timeout.

Concurrent NSOperations run asynchronously - once they are started, they return straight away. A concurrent NSOperation simply needs to mark itself as finished when it has done its work.

The new version has two main advantages:

  • Slightly lower CPU use, and slightly better performance - while using a loop to prevent the request exiting is not necessarily inefficient, ASIHTTPRequests were looping far more often than they needed to. The new version checks up on the status of a request every 1/4 of a second using a timer.
  • ASIHTTPRequests can now be run asynchronously in the main thread, without the use of a queue. [request startAsynchronous] now does just that - it runs the request in the current thread.

Persistent connections

ASIHTTPRequest now supports persistent HTTP connections. This means that when performing multiple requests to the same server, requests can re-use the connection from a previous request. This results in a significant performance boost for applications that perform many small requests to the same server.

Benchmarks

A basic set of benchmarks is now included with ASIHTTPRequest as part of the tests - you can run them yourself from the test target. Make sure you run these tests one at a time. Here are my results.

ASIHTTPRequest 1.2 - Asynchronous download
Uses global NSOperationQueue behind the scenes
3.858196 seconds
ASIHTTPRequest 1.5 - Asynchronous download
Running in main thread
2.410864 seconds
NSURLConnection - Asynchronous download 2.101507 seconds
ASIHTTPRequest 1.2 - Synchronous download
Running in main thread
19.995 seconds
ASIHTTPRequest 1.5 - Synchronous download
Running in main thread
5.273 seconds
NSURLConnection - Synchronous download 5.399 seconds
ASIHTTPRequest 1.2 - Asynchronous download using NSOperationQueue 3.803018 seconds
ASIHTTPRequest 1.5 - Asynchronous download using NSOperationQueue 3.205381 seconds

All tests measure the time it takes to download a 127KB file, not gzipped, 10 times. The synchronous tests download each file after the previous one has completed, while the asynchronous tests attempt to run all the requests at once (though in the queue test, the NSOperationQueue was set to a maximum of 4 concurrent operations). I ran each test 5 times, and took the lowest number for each test. These tests were run on Mac OS 10.6.2.

Obviously, the numbers themselves are meaningless in isolation, as network speed will vary. The important thing is how well ASIHTTPRequest performs in relation to NSURLConnection.

From my results, it looks like asynchronous performance when running in the main thread is similar to NSURLConnection (and significantly faster than ASIHTTPRequest 1.2, where asynchronous requests always run in a queue). When run using an NSOperationQueue, 1.5 offers a small performance boost over 1.2. ASIHTTPRequest 1.5 also seems to be comparible with NSURLConnection for synchronous requests, but support for persistent connections makes it more than 4.5 times the speed of ASIHTTPRequest 1.2!

API changes

There have been some important API changes - you will almost certainly need to update your code.

ASIHTTPRequest 1.2 ASIHTTPRequest 1.5
[request start] [request startSynchronous]
[request authenticationChallengeInProgress] [request authenticationNeeded]
[request needsProxyAuthentication] [request authenticationNeeded] == ASIProxyAuthenticationNeeded

Information for subclassers

If you have been subclassing ASIHTTPRequest, you may well need to make revisions to your subclass. Some of the key changes:

  • loadRequest no longer exists. The stream is now scheduled in startRequest. checkRequestStatus is now used for checking up on a request.
  • readResponseHeadersReturningAuthenticationFailure is now readResponseHeaders, and returns void. This method is now only called from in handleStreamComplete, redirection and authentication checks now only happen when the full response has been downloaded (Update - whoops, this broke stuff. Should be called from handleBytesAvailable again)
  • Delegate authentication and ASIAuthenticationDialog no longer use locks to pause a request.
  • Bandwidth throttling now works by unscheduling the read stream from the runloop, rather than sleeping the thread
  • ASIHTTPRequests no longer use a custom run loop mode
  • There is no longer a global request queue

Lots more

This release also includes NSCopying support, automatic retry on timeout, improved bandwidth throttling, Reachability 2.0 support, plus lots of other smaller features and fixes. Check out the change log for more details.

If you encounter any problems with this version of ASIHTTPRequest, please use the Google Group to let me know!

  1. On Snow Leopard, NSOperationQueues run in the GCD thread pool, rather than creating a new thread for each operation.

Posted by Ben @ 3:15 PM