{"id":32151728,"url":"https://github.com/bottlerocketstudios/ios-hyperspace","last_synced_at":"2025-10-21T10:51:37.215Z","repository":{"id":27623393,"uuid":"114586409","full_name":"BottleRocketStudios/iOS-Hyperspace","owner":"BottleRocketStudios","description":"An extremely lightweight wrapper around URLSession to make working with APIs a breeze.","archived":false,"fork":false,"pushed_at":"2025-02-06T18:59:19.000Z","size":870,"stargazers_count":48,"open_issues_count":12,"forks_count":14,"subscribers_count":12,"default_branch":"main","last_synced_at":"2025-10-14T08:47:04.011Z","etag":null,"topics":["networking","urlsession"],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/BottleRocketStudios.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-12-18T02:21:33.000Z","updated_at":"2025-04-14T17:49:27.000Z","dependencies_parsed_at":"2024-06-21T20:21:27.115Z","dependency_job_id":"b4afdf44-c7a1-494a-a91e-fee1100429c7","html_url":"https://github.com/BottleRocketStudios/iOS-Hyperspace","commit_stats":{"total_commits":589,"total_committers":16,"mean_commits":36.8125,"dds":0.5059422750424448,"last_synced_commit":"0671e2c8520c6a31db69f937a42ba87644709ca9"},"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/BottleRocketStudios/iOS-Hyperspace","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BottleRocketStudios%2FiOS-Hyperspace","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BottleRocketStudios%2FiOS-Hyperspace/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BottleRocketStudios%2FiOS-Hyperspace/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BottleRocketStudios%2FiOS-Hyperspace/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/BottleRocketStudios","download_url":"https://codeload.github.com/BottleRocketStudios/iOS-Hyperspace/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BottleRocketStudios%2FiOS-Hyperspace/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":280248569,"owners_count":26297925,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-21T02:00:06.614Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["networking","urlsession"],"created_at":"2025-10-21T10:51:34.067Z","updated_at":"2025-10-21T10:51:37.206Z","avatar_url":"https://github.com/BottleRocketStudios.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Hyperspace\n\n![CI Status](https://github.com/BottleRocketStudios/iOS-Hyperspace/actions/workflows/main.yml/badge.svg)\n[![Version](https://img.shields.io/cocoapods/v/Hyperspace.svg?style=flat)](http://cocoapods.org/pods/Hyperspace)\n[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)\n[![SwiftPM compatible](https://img.shields.io/badge/SwiftPM-compatible-4BC51D.svg?style=flat)](https://swift.org/package-manager)\n[![Platform](https://img.shields.io/cocoapods/p/Hyperspace.svg?style=flat)](http://cocoapods.org/pods/Hyperspace)\n[![codecov](https://codecov.io/gh/BottleRocketStudios/iOS-Hyperspace/branch/master/graph/badge.svg)](https://codecov.io/gh/BottleRocketStudios/iOS-Hyperspace)\n[![License](https://img.shields.io/cocoapods/l/Hyperspace.svg?style=flat)](http://cocoapods.org/pods/Hyperspace)\n\n**Hyperspace** provides a simple abstraction around URLSession and HTTP. There are a few main goals:\n\n* Keep things simple.\n* 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.\n* 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.\n\n## Contents\n\n* **HTTP** - Contains standard HTTP definitions and types. If you feel something is missing from here, please submit a pull request.\n* **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`.\n* **TransportService** - Uses a `TransportSession` (`URLSession` by default) to execute `URLRequests`. Deals with raw `HTTP` and `Data`.\n* **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**.\n\n## Usage\n\n### 1. Create Requests\n\nYou have multiple options when creating requests. These include creating static functions to reduce the boilerplate when creating a `Request` object or simply creating 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.\n\n#### Option 1 - Extending `Request` \n\nThe 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 of which are customizable) to keep the definition brief:\n\n```swift\nextension Request where Response == Post {\n    \n    static func createPost(_ post: NewPost) -\u003e Request\u003cPost\u003e {\n        return Request(method: .post, url: URL(string: \"https://jsonplaceholder.typicode.com/posts\")!, headers: [.contentType: .applicationJSON], body: try? HTTP.Body.json(post))\n    }\n}\n```\n\n#### Option 2 - Define Each `Request` Locally\n\n```swift\nlet createPostRequest: Request\u003cPost\u003e = Request(method: .post, url: URL(string: \"https://jsonplaceholder.typicode.com/posts\")!, headers: [.contentType: .applicationJSON], body: try? HTTP.Body.json(post))\n```\n\n#### Option 3 - Create a `CreatePostRequest` that wraps a `Request`\n\n```swift\nstruct CreatePostRequest {\n    let newPost: NewPost\n    \n    var request: Request\u003cPost\u003e {\n        return Request(method: .post, url: URL(string: \"https://jsonplaceholder.typicode.com/posts\")!, headers: [.contentType: .applicationJSON], body: try? HTTP.Body.json(post))\n    }\n}\n```\n\nFor the above examples, the `Post` response type and `NewPost` body are defined as follows:\n\n```swift\nstruct Post: Decodable {\n    let id: Int\n    let userId: Int\n    let title: String\n    let body: String\n}\n```\n\n```swift\nstruct NewPost: Encodable {\n    let userId: Int\n    let title: String\n    let body: String\n}\n```\n\n### 2. Create Request defaults (optional)\n\nTo 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:\n\n```swift\nRequestDefaults.defaultCachePolicy = .reloadIgnoringLocalCacheData // Default cache policy is '.useProtocolCachePolicy'\nRequestDefaults.defaultDecoder = MyCustomDecoder() // Default decoder is JSONDecoder()\n```\n\n### 3. Create a BackendService to execute your requests\n\nWe recommend adhering to the [Interface Segregation](https://en.wikipedia.org/wiki/Interface_segregation_principle) principle by creating separate \"controller\" objects for each section of the API you're communicating with. Each controller should expose a set of related functions 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:\n\n```swift\nclass ViewController: UIViewController {\n    private let backendService = BackendService()\n\n    // Rest of your view controller code...\n}\n```\n\n### 4. Instantiate your Request\n\nLet's say a view controller is supposed to create the post whenever the user taps the \"send\" button. Here's what that might look like:\n\n```swift\n@IBAction private func sendButtonTapped(_ sender: UIButton) {\n    let title = ... // Get the title from a text view in the UI...\n    let message = ... // Get the message from a text view/field in the UI...\n    let post = NewPost(userId: 1, title: title, body: message)\n\n    let createPostRequest = CreatePostRequest(newPost: post)\n\n    // Execute the network request...\n}\n```\n\n### 5. Execute the Request using the BackendService\n\nFor 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:\n\n```swift\ndo {\n    let post = NewPost(userId: 1, title: title, body: \"\")\n    let createPostRequest = Request\u003cPost\u003e.createPost(post)\n    let createdPost = try await backendService.execute(request: createPostRequest)\n    // Insert the new post into the UI...\n    \n} catch {\n    // Alert the user to the error...\n}\n```\n\n## Example\n\nClone the repo:\n\n```bash\ngit clone https://github.com/BottleRocketStudios/iOS-Hyperspace.git\n```\n\nFrom here, you can open up `Hyperspace.xcworkspace` and run the examples:\n\n### Shared Code\n\n* `Models.swift`, `Requests.swift`\n    * Sample models and network requests shared by the various examples.\n\n### Example Targets\n\n* **Hyperspace-iOSExample**\n    * `ViewController.swift`\n        * View a simplified example of how you might use this in your iOS app.\n* **Hyperspace-tvOSExample**\n    * `ViewController.swift`\n        * View a simplified example of how you might use this in your tvOS app (this is essentially the same as the iOS example).\n* **Hyperspace-watchOSExample Extension**\n    * `InterfaceController.swift`\n        * View a simplified example of how you might use this in your watchOS app.\n\n### Playgrounds\n\n* **Playground/Hyperspace.playground**\n    * View and run a single file that defines models, network requests, and executes the requests similar to the example targets above.\n* **Playground/Hyperspace_DELETE.playground**\n    * An example of how to deal with requests that don't return a result. This is usually common for DELETE requests.\n\n## Requirements\n\n* iOS 13.0+\n* tvOS 13.0+\n* watchOS 6.0+\n* macOS 11+\n* Swift 5.6\n\n## Installation\n\n### Cocoapods\n\nHyperspace is available through [CocoaPods](http://cocoapods.org). To install it, simply add the following line to your Podfile:\n\n```ruby\npod 'Hyperspace'\n```\n\n### Carthage\n\nAdd the following to your [Cartfile](https://github.com/Carthage/Carthage/blob/master/Documentation/Artifacts.md#cartfile):\n\n```\ngithub \"BottleRocketStudios/iOS-Hyperspace\"\n```\n\nRun `carthage update` and follow the steps as described in Carthage's [README](https://github.com/Carthage/Carthage#adding-frameworks-to-an-application).\n\n### Swift Package Manager\n\n```swift\ndependencies: [\n    .package(url: \"https://github.com/BottleRocketStudios/iOS-Hyperspace.git\", from: \"5.0.0\")\n]\n```\n\n## Author\n\n[Bottle Rocket Studios](https://www.bottlerocketstudios.com/)\n\n## License\n\nHyperspace is available under the Apache 2.0 license. See the LICENSE.txt file for more info.\n\n## Contributing\n\nSee the [CONTRIBUTING] document. Thank you, [contributors]!\n\n[CONTRIBUTING]: CONTRIBUTING.md\n[contributors]: https://github.com/BottleRocketStudios/iOS-Hyperspace/graphs/contributors\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbottlerocketstudios%2Fios-hyperspace","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbottlerocketstudios%2Fios-hyperspace","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbottlerocketstudios%2Fios-hyperspace/lists"}