foursquare iOS networking library

Overview

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:

  • Asynchronous HTTP and HTTPS requests.
  • GET and POST (urlencoded and form data); easily extended for other HTTP methods.
  • Response parsing in a background thread, followed by a completion block on the main thread.
  • Convenient, safe object retention sementics and life cycle management, enabling fire-and-forget requests.
  • Support for iOS background tasks.
  • Useful utilities for creating and debugging form data, parsing JSON, error checking, etc.
  • Clean, straightforward implementation; no class hierarchies to grok. Easily trace and understand the life cycle of your connection!

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:

  • The value returned from the parse block is assigned to the connection's parseResult property.
  • Similarly, if any error is set by the parse block, that is assigned to the connection's error property.
  • You can write simple wrappers around this method to accommodate all the endpoints in a web API:
    • Compose a URL for each endpoint.
    • Send standard headers such as User-Agent and Accept-Language.
    • Send standard parameters such as OAuth token, client version, etc.
    • Combine standard and custom handling for parse and completion steps by calling small custom blocks inside of wrapper blocks that perform uniform error-checking, parsing, etc.

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.

  • If the POST consists of only NSString and NSNumber values, then it will have the content-type "application/x-www-form-urlencoded".
  • If any parameter is an FSNData object, then the request will have the content-type "multipart/form-data".

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.

  • As is, the connection will fail with an OAuth error, demonstrating the error handling conventions.
  • To see the connection succeed, sign up for an OAuth token at http://developer.foursquare.com, or else just request some static html page.

FSNDemo-mac shows how to make the exact same connection, but from the command line.

  • Running an asynchronous connection requires the program to run the main runloop manually.

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!

Comments
  • Add cocoapods spec

    Add cocoapods spec

    Hey foursquare people!

    I love the lightweight design of FSNetworking and we're looking to use it on a new iOS project at HubSpot. We use CocoaPods for dependency management -- could you add this podspec to your repo so people can pull it in with CocoaPods instead of manually inserting the files?

    (Right now, it links to my fork of the repo, but once the Podspec is in yours, I imagine we'd want to move it over.)

    opened by aroldan 7
  • cancel specific request

    cancel specific request

    Hello i am trying to add many upload requests to a "queue". in your example you do: self.connection = [self makeConnection]; [self.connection start];

    i have 2 questions: 1st -> how can i upload many requests to a single queue? 2nd -> how can i cancel a specific one from the queue?

    thanks a lot

    opened by mythodeia 5
  • URL is Invalid

    URL is Invalid

    I am trying to integrate FSNetworking into my application. When I debug [FSNConnection start], I see the URL is invalid and I get the error "message sent to deallocated instance." When printed, I get: (lldb)po self.url (NSURL *) $4 = 0x07e81150 [no Objective-C description available]. The NSAssert(self.url, @"nil url") passes, I am guessing this is a dangling pointer.

    My project is compiled with ARC enabled, and I set -fno-objc-arc as suggested in the readme file.

    opened by mdewolfe 4
  • arrayFromJSONWithError: plus small fixes for Mac

    arrayFromJSONWithError: plus small fixes for Mac

    I have added a convenience method arrayFromJSONWithError: for the sake of completeness, in an effort to at least clarify issue #5. Additionally, I have silenced recent warnings for the demo projects.

    opened by gwk 3
  • FSNData and Straight C Code

    FSNData and Straight C Code

    I am adding a few things to FSNData, and I would like to know the why straight C code was used for NSString *stringForMimeType(MimeType type); over a class level method. Is it a preferred style, or some other reason?

    opened by mdewolfe 2
  • arrayFromJSONWithError for NSData category

    arrayFromJSONWithError for NSData category

    Along with this method:

    
    // convenience method to ensure that top-level json object is a dictionary
    - (id)dictionaryFromJSONWithError:(NSError **)error; // error pointer must not be nil
    

    it would be really great if you provide arrayFromJSONWithError that internally calls this one and returns NSArray of NSDictionary instances from JSON, that would be good. I guess you also have such a need in 4sq API. Am I missing an obvious workaround?

    Thanks.

    opened by ahmetb 2
  • Please pull in additional refactors in the outstanding pull request.

    Please pull in additional refactors in the outstanding pull request.

    There is stuff accumulating in my fork that i would like to see upstream; most recently I did a small refactor of adam's isAcceptableValue changes and he asked me to open an issue. It is all very straightforward stuff.

    My change set does not fix the missing NSArray urlEncodedString implementation, but it does add one for NSNumber that replaces the old inlined conditional logic.

    Thanks! -g

    opened by gwk 1
  • Incorrect empty line before next boundary

    Incorrect empty line before next boundary

    No empty line / seperator after parameter value before the next boundary. http://www.ietf.org/rfc/rfc1867.txt doesn't specify this newline. This is an issue when uploading an object upload to Amazon's S3 API. It seems their HTTP server treats the newline as part of the value, which is correct according to the RFC. Removing the empty line made the object upload work.

    opened by wardbekker 1
  • Post sub-array params

    Post sub-array params

    Hi guys, i went back in this problem, basically we can't post NSArray as params, so, i thought to create an NSMutableDictionary variable, and dynamically putting in it all my params, creating subarray, like this 'params[object][][field1]' and so on. But my problem is that him is overwriting always the same value, not creating subarray. :-1: i tried to create params like this: [NSString stringWithFormat:@"supply[%i][description]", i], but is really ugly.

    any better ideas to solve this problem? any workaround much better? thanks guys.

    opened by antonioparisi 0
  • Fix warning: Implicit conversion loses integer precision: 'NSUInteger…

    Fix warning: Implicit conversion loses integer precision: 'NSUInteger…

    FSNConnection.m:523:36: Implicit conversion loses integer precision: 'NSUInteger' (aka 'unsigned long') to 'int'

    image

    Reason: The count method of NSArray returns an NSUInteger.

    I changed type of concurrencyCountAtStart to NSUInteger.

    opened by koogawa 0
  • Fix #8: Race condition in FSNConnection

    Fix #8: Race condition in FSNConnection

    We believe that #8 is caused by a race between a failing parse block in the background thread and a background task expiration triggered by UIKit.

    In the event that this happens, an assertion exception is not appropriate. The user will not be able to catch this exception in a reasonable way, and the user will typically prefer to be notified of at least one error via c.error in the completion block.

    @gwk, does this seem reasonable to you? We are hoping this will help us with this issue.

    opened by braintreeps 1
  • [self.connection cancel] cancel other connections?

    [self.connection cancel] cancel other connections?

    Hi I experience some problems with [self.connection cancel] in the dealloc method. I have a feeling it sometimes also cancel other ongoing request in the queue. Is this possible ? I have experimented both with and without FSN_QUEUED_CONNECTIONS.

    - (void)dealloc {
    
        NSAssert(!self.connection, @"non-nil connection: %@", self.connection);
    
    #if TARGET_OS_IPHONE
        // if this task was set to run in background then the expiration handler should be retaining self
        NSAssert1(self.taskIdentifier == UIBackgroundTaskInvalid,
                  @"deallocated request has background task identifier: %@", self);
    #endif
    
    
        [self clearBlocks]; // not cleanup; assert no taskIdentifer above instead
    
        // just to be safe in production
        FSNVerbose(@"%p: dealloc", self);
    
        [self.connection cancel];
    }
    
    opened by knutigro 1
  • BUG Running FSNetworking in background threads

    BUG Running FSNetworking in background threads

    I don't know if its supported, but it seems to work. Mostly.

    In FSNConnection, there is the mutableset of current connections.

    mutableConnections

    It is a NSMutableSet and is a global, but accessed by eac h object with no @synchronize() {} block.

    So the code need to change in two places -

    In cleanup

    NSMutableSet *requests = [self.class mutableConnections];
    @synchronized(requests) {
        [requests removeObject:self];
    }
    

    And in start NSMutableSet *connections = [self.class mutableConnections]; @synchronized(connections) { [connections addObject:self]; }

    I got a crash (as would make sense) when using about 3 of these on threads on 10.8.4 Mac, with XCode 5. With Zombies and what not on, it pointed right at the cleanup line.

    --Tom

    opened by tomandersen 4
  • Please add semantic version tags

    Please add semantic version tags

    I’ve recently added FSNetworking to the CocoaPods package manager repo.

    CocoaPods is a tool for managing dependencies for OS X and iOS Xcode projects and provides a central repository for iOS/OS X libraries. This makes adding libraries to a project and updating them extremely easy and it will help users to resolve dependencies of the libraries they use.

    However, FSNetworking doesn't have any version tags. I’ve added the current HEAD as version 0.0.1, but a version tag will make dependency resolution much easier.

    Semantic version tags (instead of plain commit hashes/revisions) allow for resolution of cross-dependencies.

    In case you didn’t know this yet; you can tag the current HEAD as, for instance, version 1.0.0, like so:

    $ git tag -a 1.0.0 -m "Tag release 1.0.0" $ git push --tags

    opened by dburr 1
Owner
Foursquare
Foursquare
QwikHttp is a robust, yet lightweight and simple to use HTTP networking library for iOS, tvOS and watchOS

QwikHttp is a robust, yet lightweight and simple to use HTTP networking library. It allows you to customize every aspect of your http requests within a single line of code, using a Builder style syntax to keep your code super clean.

Logan Sease 2 Mar 20, 2022
A networking library for iOS, macOS, watchOS and tvOS

Thunder Request Thunder Request is a Framework used to simplify making http and https web requests. Installation Setting up your app to use ThunderBas

3 SIDED CUBE 16 Nov 19, 2022
🏇 A Swift HTTP / HTTPS networking library just incidentally execute on machines

Thus, programs must be written for people to read, and only incidentally for machines to execute. Harold Abelson, "Structure and Interpretation of Com

John Lui 845 Oct 30, 2022
Malibu is a networking library built on promises

Description Palm trees, coral reefs and breaking waves. Welcome to the surf club Malibu, a networking library built on promises. It's more than just a

Vadym Markov 410 Dec 30, 2022
RSNetworking is a networking library written entirly for the Swift programming language.

RSNetworking is a networking library written entirly for the Swift programming language.

null 18 Feb 25, 2018
ServiceData is an HTTP networking library written in Swift which can download different types of data.

ServiceData Package Description : ServiceData is an HTTP networking library written in Swift which can download different types of data. Features List

Mubarak Alseif 0 Nov 11, 2021
Swish is a networking library that is particularly meant for requesting and decoding JSON via Decodable

Swish Nothing but net(working). Swish is a networking library that is particularly meant for requesting and decoding JSON via Decodable. It is protoco

thoughtbot, inc. 369 Nov 14, 2022
Malibu is a networking library built on promises

Description Palm trees, coral reefs and breaking waves. Welcome to the surf club Malibu, a networking library built on promises. It's more than just a

HyperRedink 10 Jan 29, 2022
A networking library for Swift

Nikka Nikka is a super simple Swift HTTP networking library that comes with many extensions to make it modular and really powerful. Installation Usage

Emilien Stremsdoerfer 29 Nov 4, 2022
AsyncHTTP - Generic networking library written using Swift async/await

Generic networking library written using Swift async/await

Laszlo Teveli 7 Aug 3, 2022
A resource based, protocol oriented networking library designed for pure-SwiftUI applications.

Monarch ?? - WIP A resource based, protocol oriented networking library designed for pure-SwiftUI applications. Features: Async/Await Resource Based P

Emilio Pelaez Romero 4 Oct 17, 2022
🌏 A zero-dependency networking solution for building modern and secure iOS, watchOS, macOS and tvOS applications.

A zero-dependency networking solution for building modern and secure iOS, watchOS, macOS and tvOS applications. ?? TermiNetwork was tested in a produc

Bill Panagiotopoulos 90 Dec 17, 2022
A delightful networking framework for iOS, macOS, watchOS, and tvOS.

AFNetworking is a delightful networking library for iOS, macOS, watchOS, and tvOS. It's built on top of the Foundation URL Loading System, extending t

AFNetworking 33.3k Jan 5, 2023
Extensible HTTP Networking for iOS

Bridge Simple Typed JSON HTTP Networking in Swift 4.0 GET GET<Dict>("http://httpbin.org/ip").execute(success: { (response) in let ip: Dict = respo

null 90 Nov 19, 2022
Lightweight Networking and Parsing framework made for iOS, Mac, WatchOS and tvOS.

NetworkKit A lightweight iOS, Mac and Watch OS framework that makes networking and parsing super simple. Uses the open-sourced JSONHelper with functio

Alex Telek 30 Nov 19, 2022
Bonjour networking for discovery and connection between iOS, macOS and tvOS devices.

Merhaba Bonjour networking for discovery and connection between iOS, macOS and tvOS devices. Features Creating Service Start & Stop Service Stop Brows

Abdullah Selek 67 Dec 5, 2022
An elegant yet powerful iOS networking layer inspired by ActiveRecord.

Written in Swift 5 AlamoRecord is a powerful yet simple framework that eliminates the often complex networking layer that exists between your networki

Tunespeak 19 Nov 19, 2022
Elegant HTTP Networking in Swift

Alamofire is an HTTP networking library written in Swift. Features Component Libraries Requirements Migration Guides Communication Installation Usage

Alamofire 38.7k Jan 8, 2023
Type-safe networking abstraction layer that associates request type with response type.

APIKit APIKit is a type-safe networking abstraction layer that associates request type with response type. // SearchRepositoriesRequest conforms to Re

Yosuke Ishikawa 1.9k Dec 30, 2022