Home

Awesome

FSNetworking

foursquare's 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.

Features

FSNConnection offers the following:

Examples

GET


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

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.

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.

Demos

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.

License

FSN is released under the Apache License, Version 2.0; see license.txt. More information can be found here:

Releases

The current release is 1.0. This code has been in production in the foursquare app for many moons.

Known Issues

Delegate Queues

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:

Recursive Lock

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.

Dependencies

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.

Maintainers

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!