An extremely lightweight wrapper around URLSession to make working with APIs a breeze.

What's New



4.0.0 (2022-01-14)


  • Make TransportService thread-safe
    Daniel Larsen

  • Added async / await functions for executing requests
    Daniel Larsen

  • Migrate to use .xcframework's when resolving depencies using Carthage
    Will McGinty

  • Add acceptedRange to HTTP.Status subtypes and allow for individual subtypes to be detected.
    Will McGinty

  • Strongly type HTTP.Request.headers and HTTP.Response.headers as [HTTP.HeaderKey: HTTP.HeaderValue].
    Will McGinty

  • Add functionality to consider an arbitrary TransportFailure a success, using Request.recoveryTransformer.
    Will McGinty

  • Add an overload to map which passes along the TransportSuccess as well as the original Response.
    Will McGinty

  • Migrate the request recovery strategy to the BackendServiceProtocol definition.
    Will McGinty

  • Added method parameter to HTTP.Request.
    Alex Reyes

  • Changed underlying error in AnyError's NetworkServiceFailureInitializable implementation from NetworkServiceError to NetworkServiceFailure so it can return its failure response rather than nil.
    Richard Burgess

  • Finished migrating all targets to Swift 5.
    Tyler Milner

  • Added Carthage support.
    Ryan Gant

  • Added Swift Package Manager support.
    Ryan Gant

  • Migrate the request recovery strategy to the BackendServiceProtocol definition.
    Will McGinty

  • Rename RequestRecoveryStrategy to RecoveryStrategy and allow multiple to be attached to a single BackendService. They are executed in the order they are initialized.
    Will McGinty

  • Rename DecodingFailureInitializable to DecodingFailureRepresentable and make the failing HTTP.Response available during initialization.
    Will McGinty

  • Create an HTTP.Body type to abstract the Data of a URLRequest.
    Will McGinty

  • Several changes to simplify and refine DecodableContainer, as well as introduce EncodableContainer and CodableContainer.
    Will McGinty

  • Convert Request protocol into a struct and eliminate the AnyRequest type. A URLRequestCreationStrategy has been created to allow for differences in URLRequest generation.
    Will McGinty

  • Rename Network* to Transport* to provide a clearer distinction between the role of the BackendService and TransportService.
    Will McGinty

  • Utilize URLError as part of the Transporting protocol to allow for more granularity and detail in error reporting.
    Will McGinty

  • Make TransportError inits public.
    Earl Gaspard

  • Create a form URL encoded HTTP.Body convenience
    Will McGinty

  • Add deprecated typealias to ease migration to 4.0
    Will McGinty

  • Create EmptyDecodingStrategy to add flexibility to decoding EmptyResponse
    Will McGinty

  • Add defaultDecoder to RequestDefaults and use when initializing a Request.
    Earl Gaspard

  • Make transportService public in BackendService.
    Earl Gaspard

  • Make DecodingFailure.Context properties public.
    Earl Gaspard

  • Add defaultMaxRecoveryAttempts to RequestDefaults and use for maxRecoveryAttempts in Request. This changes the default retries from unlimited to 1.
    Earl Gaspard

Bug Fixes
  • Add an assertion to BackendService if a GET HTTP request with body data is detected.
    Will McGinty


CI Status Version Carthage compatible License Platform codecov codebeat badge


This library provides a simple abstraction around URLSession and HTTP. There are a few main goals:

  • Keep things simple.
  • Keep the overall library size to a minimum. Of course, there will be some boilerplate involved (such as the HTTP definitions), but our main goal is to keep the library highly functional and maintainable without over-engineering.
  • Tailor the library to the networking use cases that we encounter the most often. We will continue to add features based on the common needs across all of the apps that we build.

Key Concepts

  • HTTP - Contains standard HTTP definitions and types. If you feel something is missing from here, please submit a pull request!
  • Request - A struct that defines the details of a network request, including the desired result and error types. This is basically a thin wrapper around URLRequest, utilizing the definitions in HTTP.
  • TransportService - Uses a TransportSession (URLSession by default) to execute URLRequests. Deals with raw HTTP and Data.
  • BackendService - Uses a TransportService to execute Requests. Transforms the raw Data returned from the TransportService into the response model type defined by the Request. This is the main worker object your app will deal with directly.


1. Create Requests

You have multiple options when creating requests- including creating static functions to reduce the boilerplace when creating a Request object, or you can simply create them locally. In addition, you can still create your own custom struct that wraps and vends a Request object if your network requests are complex.

Option 1 - Extending Request

The example below illustrates how to create an extension on Request which can drastically reduce the boilerplate when creating a request to create a new post in something like a social network feed. It takes advantage of the many defaults into Request (all are which are customizable) to keep the definition brief:

extension Request {
    static func createPost(_ post: NewPost) -> Request<Post, AnyError> {
        return Request(method: .post, url: URL(string: "https://jsonplaceholder.typicode.com/posts")!, headers: [.contentType: .applicationJSON],
                       body: try? HTTP.Body(post))

Option 2 - Define Each Request Locally

let createPostRequest = Request(method: .post, url: URL(string: "https://jsonplaceholder.typicode.com/posts")!, headers: [.contentType: .applicationJSON],
        body: try? HTTP.Body(post))

Option 3 - Create a CreatePostRequest that wraps a Request

struct CreatePostRequest {
    let newPost: NewPost
    var request: Request<Post, AnyError> {
        return Request(method: .post, url: URL(string: "https://jsonplaceholder.typicode.com/posts")!, headers: [.contentType: .applicationJSON],
                       body: try? HTTP.Body(post))

For the above examples, the Post response type and NewPost body are defined as follows:

struct Post: Decodable {
    let id: Int
    let userId: Int
    let title: String
    let body: String
struct NewPost: Encodable {
    let userId: Int
    let title: String
    let body: String

2. Create Request defaults (optional)

To avoid having to define default Request property values for every request in your app, it can be useful to rely on the RequestDefaults provided by Hyperspace. These can even be customized:

RequestDefaults.defaultCachePolicy = .reloadIgnoringLocalCacheData // Default cache policy is '.useProtocolCachePolicy'
RequestDefaults.defaultDecoder = MyCustomDecoder() // Default decoder is JSONDecoder

3. Create a BackendService to execute your requests

We recommend adhering to the Interface Segregation principle by creating separate "controller" objects for each section of the API you're communicating with. Each controller should expose a set of related funtions and use a BackendService to execute requests. However, for this simple example, we'll just use BackendService directly as a private property on the view controller:

class ViewController: UIViewController {

    private let backendService = BackendService()

    // Rest of your view controller code...

4. Instantiate your Request

Let's say our view controller is supposed to create the post whenever the user taps the "send" button. Here's what that might look like:

@IBAction private func sendButtonTapped(_ sender: UIButton) {
    let title = ... // Get the title from a text view in the UI...
    let message = ... // Get the message from a text view/field in the UI...
    let post = NewPost(userId: 1, title: title, body: message)

    let createPostRequest = CreatePostRequest(newPost: post)

    // Execute the network request...

5. Execute the Request using the BackendService

For the above example, here's how you would execute the request and parse the response. While all data transformation happens on the background queue that the underlying URLSession is using, all BackendService completion callbacks happen on the main queue so there's no need to worry about threading before you update UI. Notice that the type of the success response's associated value below is a Post struct as defined in the CreatePostRequest above:

backendService.execute(request: createPostRequest) { [weak self] result in
    debugPrint("Create post result: \(result)")

    switch result {
    case .success(let post):
        // Insert the new post into the UI...
    case .failure(let error):
        // Alert the user to the error...


Clone the repo:

git clone https://github.com/BottleRocketStudios/iOS-Hyperspace.git

From here, you can open up Hyperspace.xcworkspace and run the examples:

Shared Code

  • Models.swift, Requests.swift
    • Sample models and network requests shared by the various examples.

Example Targets

  • Hyperspace-iOSExample
    • ViewController.swift
      • View a simplified example of how you might use this in your iOS app.
  • Hyperspace-tvOSExample
    • ViewController.swift
      • View a simplified example of how you might use this in your tvOS app (this is essentially the same as the iOS example).
  • Hyperspace-watchOSExample Extension
    • InterfaceController.swift
      • View a simplified example of how you might use this in your watchOS app.


  • Playground/Hyperspace.playground
    • View and run a single file that defines models, network requests, and executes the requests similar to the example targets above.
  • Playground/Hyperspace_AnyRequest.playground
    • The same example as above, but using the AnyRequest<T> struct.
  • Playground/Hyperspace_DELETE.playground
    • An example of how to deal with requests that don't return a result. This is usually common for DELETE requests.


  • iOS 8.0+
  • tvOS 9.0+
  • watchOS 2.0+
  • Swift 5.0



Hyperspace is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod 'Hyperspace'


Add the following to your Cartfile:

github "BottleRocketStudios/iOS-Hyperspace"

Run carthage update and follow the steps as described in Carthage's README.

Swift Package Manager

dependencies: [
    .package(url: "https://github.com/BottleRocketStudios/iOS-Hyperspace.git", from: "3.2.1")


Bottle Rocket Studios


Hyperspace is available under the Apache 2.0 license. See the LICENSE.txt file for more info.


  • Swift Tools 5.0.0
View More Packages from this Author


  • None
Last updated: Sun Nov 20 2022 16:47:48 GMT-0500 (GMT-05:00)