Nate Stedman

Shirley

December 30th, 2015

Shirley is a minimal request framework for Swift programs. It uses ReactiveCocoa signal producers as a unified method for asynchronous data delivery, instead of callback functions. This allows disparate responses to be unified, with a single stream-based system.

Sessions

The central type of the framework is a session, represented by the SessionType protocol. Session is a closure-based implementation, which can also be used for type erasure. Sessions can be thought of as “signal producer producers” – they implement a single function that transforms a request value into a signal producer:

let session = ...
let request = ...

session.producerForRequest(request)
    .startWithNext({ value in
        ...
    })

SessionType also requires type aliases for Request, Value, and Error, so that the type signature for producerForRequest(:) can be defined:

func producerForRequest(request: Request)
    -> SignalProducer<Value, Error>

Transforms

Using producerForRequest(:) alone requires response values to be interpreted as SessionType declares. Shirley solves this issue by allowing sessions to be transformed into derived sessions. For example, let’s say we have User and Post types, and we want to create sessions that will process a JSON representation of each:

let baseSession = ...

// transform the session into one which produces JSON
// assume that we have a "JSON" type
let JSONSession = baseSession
    .transform({ data in
        if let json = JSON(data: data)
        {
            return SignalProducer(value: json)
        }
        else
        {
            return SignalProducer(error: Error.CouldNotParseJSON)
        }
    })

// transform the JSON session into one which produces Posts
let postSession = JSONSession
    .transform({ json in
        if let post = Post.parse(json)
        {
            return SignalProducer(value: post)
        }
        else
        {
            return SignalProducer(error: Error.CouldNotParseUser)
        }
    })

// transform the JSON session into one which produces Users
let userSession = JSONSession
    .transform({ json in
        if let user = User.parse(json)
        {
            return SignalProducer(value: user)
        }
        else
        {
            return SignalProducer(error: Error.CouldNotParseUser)
        }
    })

// process requests
postSession.producerForRequest(...)
    .startWithNext({ post in
        ...
    })
    
userSession.producerForRequest(...)
    .startWithNext({ user in
        ...
    })

This creates four sessions, all of which use baseSession to execute their requests. The request type can also be transformed. Let’s assume that User values have an identifier, and we’d like to be able to request a user by its identifier:

let identifierSession = userSession
    .transformRequest({ (identifier: String) in
        ...
    })

identifierSession.producerForRequest("0")
    .startWithNext({ user in
        ...
    })

Integration with Foundation

Although Shirley is generic, it provides support for NSURLSession, which is extended to conform to SessionType. NSURLSession accepts requests of type NSURLRequest, produces values of type Message<NSURLResponse, NSData>, and produces errors of type NSError. Message is a small container type provided by Shirley for the NSURLSession extensions. Like Session, messages are backed by a protocol and are generic, so they are not limited to use with NSURLSession.

Additionally, some of the extensions to SessionType are valuable when working with NSURLSession:

  • HTTPSession() will transform Message<NSURLResponse, Body> into Message<NSHTTPURLResponse, Body>, failing with an error if the response is not of type NSHTTPURLResponse.
  • JSONSession(:) will transform Message<Response, NSData> into Message<Response, AnyObject>, using NSJSONSerialization, failing with the error provided by NSJSONSerialization if the data is not valid JSON.
  • bodySession() will transform Message<Response, Body> into Body, dropping the response value.

Of course, custom transforms can also be applied to NSURLSession-derived sessions.

Installation

Shirley is available from Github, and it supports Carthage for dependency management. To install with Carthage, add this line to your Cartfile:

github "natestedman/Shirley"

Shirley requires ReactiveCocoa 4.0.