foursquare iOS networking library
FSN is a small library for HTTP networking on iOS. It comprises a single class, FSNConnection, and several small Cocoa Foundation categories. FSNConnection uses NSConnection, blocks, and NSConnection’s operation queue delegate or GCD.
FSNConnection offers the following:
NSURL *url = ...; // required
NSDictionary *headers = ...; // optional
NSDictionary *parameters = ...; // optional
FSNConnection *connection =
[FSNConnection withUrl:url
method:FSNRequestMethodGET
headers:headers
parameters:parameters
parseBlock:^id(FSNConnection *c, NSError **error) {
return [c.responseData dictionaryFromJSONWithError:error];
}
completionBlock:^(FSNConnection *c) {
NSLog(@"complete: %@\n error: %@\n parseResult: %@\n", c, c.error, c.parseResult);
}
progressBlock:^(FSNConnection *c) {
NSLog(@"progress: %@: %.2f/%.2f", c, c.uploadProgress, c.downloadProgress);
}];
[connection start];
The most important aspects of this pattern are:
For example, the foursquare app defines a category on FSNConnection that looks like this:
@implementation FSNConnection (FS)
// convenience accessor property that casts parseResult to our custom, API-specific type.
- (ApiResult *)apiResult {
return self.parseResult;
}
- (ApiResult *)makeApiResultWithError:(NSError **)error {
// parse the foursquare API result JSON.
// then check self.response.statusCode, as well as the foursquare API result 'meta' JSON dict for errors.
// if everything is OK, then return the an ApiResult instance, which contains api-specific properties.
// otherwise, set *error and return nil.
...
}
// wrap an arbitrary completionBlock with standard handling behavior.
- (void)finishWithBlock:(FSNCompletionBlock)completionBlock displayError:(BOOL)displayError {
ASSERT_MAIN_THREAD;
if (self.error) {
// debug-build only error reporting.
FSLog(@"request error: %@ -- %@ -- %@ -- %@",
self.error, self.apiResult.errorDetail, self.apiResult.errorMessage, self.apiResult.errorType);
}
// perform custom block
if (completionBlock) {
completionBlock(self);
}
if (self.error && displayError) {
// display standard error UI.
...
}
// send standard notifications last.
...
}
// most foursquare API requests are constructed with this method.
// it standardizes some elements of request construction, and passes through custom parameters.
// note how we wrap the custom completionBlock with standard behavior by virtue of an intermediate method;
// this allows us to precisely control when the custom callback happens.
+ (id)withEndpoint:(NSString *)endpoint
method:(FSNRequestMethod)method
parameters:(NSDictionary *)parameters
displayError:(BOOL)displayError
parseBlock:(FSNParseBlock)parseBlock
completionBlock:(FSNCompletionBlock)completionBlock {
// note: FSAPI is a singleton defining API-related properties, defined elsewhere.
return [self withUrl:[[FSAPI shared] urlForEndpoint:endpoint]
method:method
headers:[FSAPI shared].standardRequestHeaders // headers are the same for every request
parameters:[[FSAPI shared] completeParameters:parameters] // add standard parameters like OAuth token
parseBlock:parseBlock
completionBlock:^(FSNConnection *c) {
[c finishWithBlock:completionBlock displayError:displayError];
}
progressBlock:nil];
}
// a second wrapper constructor standardizes parseBlock implementa
// most requests are constructed with this method.
+ (id)withEndpoint:(NSString*)endpoint
method:(FSNRequestMethod)method
parameters:(NSDictionary*)parameters
displayError:(BOOL)displayError
completionBlock:(FSNCompletionBlock)completionBlock {
return [self withEndpoint:endpoint
method:method
parameters:parameters
displayError:displayError
parseBlock:^(FSNConnection *c, NSError **error) {
return [c makeApiResultWithError:error];
}
completionBlock:completionBlock];
}
@end
POST requests are made using the same class and calls as GET. This uniformity is one of the most satisfying aspects of the library. POST parameter values can be any of three types: NSString, NSNumber, and FSNData.
This design allows us to rapidly adjust to changing web API requirements with minimal code changes, and eases form request implementation.
As an example, a photo upload might look like this:
UIImage *originalImage = ...;
// form file name and parameter name would be determined by the web API
NSDictionary *parameters =
[NSDictionary dictionaryWithObjectsAndKeys:
[FSNData withImage:originalImage jpegQuality:.75 fileName:@"fileName"], @"paramName",
nil];
FSNConnection *connection =
[FSNConnection withUrl:url
method:FSNRequestMethodPOST
headers:headers
parameters:parameters
parseBlock:nil
completionBlock:nil
progressBlock:nil];
The FSNData class has several other constructors for sending raw NSData; please see the header for more details.
MIME types are represented as an enumeration to encourage standards-compliance and reduce the risk of typos in string literals. Currently, only two MIME types are enumerated, but more can be added easily; just define additional enumerations and their corresponding strings in FSNData.h and FSNData.m.
Other HTTP methods like HEAD and PUT are not yet supported, but adding them should not be hard. Patches are welcome; feel free to get in touch if you would like to discuss the implementation.
FSNDemo-iOS shows how to set up a single connection to the foursquare API.
FSNDemo-mac shows how to make the exact same connection, but from the command line.
FSN is released under the Apache License, Version 2.0; see license.txt. More information can be found here:
The current release is 1.0. This code has been in production in the foursquare app for many moons.
Support for NSURLConnection’s setDelegateQueue exists but is disabled by default because it causes iOS 5 applications to deadlock. Instead, FSN uses the main thread for connection callbacks and GCD to perform parsing on a background thread. Define FSN_QUEUED_CONNECTIONS to 1 (typically in your prefix header) to use delegate queues.
Since delegate queues appear to work in Lion, the Mac demo does use delegate queues. However, this has been tested only minimally. If you enable this and find bugs, please submit patches!
See also:
An NSRecursiveLock is used to guard the parseBlock against cancellation/deallocation while in concurrent usage. We would prefer a lock-free implementation for the sake of simplicity, and we welcome any scrutiny or suggestions for a better solution.
FSNetworking depends only on Cocoa’s Foundation framework; convenience methods using UIKit are guarded appropriately. We currently build against iOS 5.0 with the latest public release of the Xcode toolset. The Mac demo builds against OS X 10.7.
FSNConnection was initially developed by Foursquare Labs as a replacement for ASIHTTPRequest in our iOS application. We now use it for all HTTP networking in the foursquare iOS app.
The current maintainer is:
Feedback, bug reports, and code contributions are all welcome!