Say hello to FSNetworking: A Small Networking Library for iOS and Mac
For almost two years the foursquare iOS application depended on ASIHTTPRequest for all of its networking transactions. It served us well, but over time we found that our needs had diverged from what the project offered. After looking at the open alternatives available, we decided to write our own networking class, using NSURLConnection and blocks for callbacks (ASIHTTPRequest was deprecated by its authors shortly thereafter). AFNetworking was released while FSNConnection was in its infancy; we considered switching over but felt that we could come up with an even simpler design (note that AFNetworking has progressed considerably since our initial assessment, and it is fascinating to see just how much our solutions have converged). Since network transactions are the drivetrain of our entire application, it was crucial that we understand the entire mechanism top to bottom.
We are very grateful to the authors of ASIHTTPRequest for all their hard work and generosity. Maintaining an open source project with so many users, who have so many different needs, is a huge undertaking, and projects like ASIHTTPRequest and AFNetworking underpin the success of countless applications. It is in this same spirit that we have chosen to open-source our own solution.
Design and Implementation
The FSNConnection class underwent a number of transformations before reaching its present form. We knew from the start that we wanted result parsing to occur on a background thread; otherwise parsing and caching callbacks would cause congestion in the main run loop and would degrade the user experience. As is so often the case, safe multithreading became the dominant concern throughout the project.
The first version had a request manager singleton, which owned a single background request thread. This thread ran its own private run loop, on which we scheduled every NSURLConnection. The implementation installed a dummy NSPort object to prevent the run loop from exiting, a trick that worked but turned out to be quite brittle. If a connection callback ever crashed on the background thread, then the thread would die but the application would not notice, and subsequent requests would silently pile up.
Rather than delve into the nasty details of detecting crashed threads and restarting them, we converted FSNConnection into an NSOperation subclass, hoping that NSOperationQueue could handle the threading semantics for us. Subclassing came with a certain amount of implementation overhead, and as we got further along the design made less and less sense. The root of the problem was that NSOperationQueue and NSRunLoop provide two distinct paradigms for asynchronous operations, and we were attempting to use both. Scheduling the underlying NSURLConnection on the main runloop seemed to sacrifice some of the efficiency that multithreading promised; creating a new runloop for each operation felt heavy handed and overly complicated. We toyed around with both approaches, but neither was satisfactory.
At some point someone noticed that NSURLConnection had a setDelegateQueue: method in the iOS headers; it was listed in the OS X 10.7 API docs, but at the time was omitted from the iOS 5 docs. This approach held great promise: let Cocoa handle the calling back to the connection delegate inside of a background queue thread. With this method FSNConnection could benefit from an operation queue without having to subclass NSOperation. Unfortunately, the undocumented method proved treacherous; setting the delegate queue on iOS 5 causes the queue to deadlock, and the connection never calls back. This bug has been discussed elsewhere; see the FSNetworking README.md for more details.
Since delegate queues work fine on OS X 10.7, we decided to stick with this approach and work around the problem on iOS 5 by scheduling the NSURLConnection in the main runloop, and then using GCD to dispatch the parse block on the background thread. We found that using the main thread for NSURLConnection callbacks did not slow down the UI too much, and GCD performs wonderfully. We have since verified that iOS 6 fixes the delegate queue bug, so we may deprecate this workaround at some point.
Switching to FSNConnection simplified our networking stack considerably. This was mostly due to the fact that we had previously used a custom request class to wrap ASIHTTPRequest, a design necessitated by the fact that in ASIHTTPRequest, form POSTs are performed by a special subclass. Since FSNConnection does GET, url-encoded POST, and multipart/form-data POST with the same API, the wrapper class became unnecessary. Instead, we simply created a category for all of our foursquare API requests. This category defines constructor class methods that return connection instances configured for a particular need. See the README.md for examples.
The other major benefit of ridding ourselves of the wrapper class was that it enabled a fire-and-forget pattern, in which requests are started, autoreleased, and then never referenced by application code again. The wrapper owned its ASIHTTPRequest object, whereas fire-and-forget required the underlying request to own its wrapper. In contrast, FSNConnection makes this work reliably by relying on the fact that NSURLConnection retains its delegate (which is unusal for Cocoa); once the underlying Cocoa connection completes or fails, the FSN completion block is called, and only after that finishes is the FSNConnection released and deallocated.
While FSNConnection serves our current needs, there are several areas that are likely to get attention from the open source community. Additional HTTP methods and MIME types can be added as needed; these should be straightforward additions. The ability to stream upload and download data from and to disk would be very useful. Another interesting feature request is the ability to instrument the authentication callbacks, mostly for the purpose of profiling SSL requests.
Since the code base is so small, we hope that it will appeal to developers who want to add their own features quickly, and we look forward to patch submissions and pull requests on Github. However, please note that we have intentionally limited the scope of this project to Cocoa networking; we would prefer that it become a component of more ambitious frameworks, rather than see it accumulate many optional components itself.
Thanks, and happy hacking!
- George King, foursquare iOS Engineer