This library provides a simple abstraction around URLSession and HTTP. There are a few main goals:
HTTP
definitions), but our main goal is to keep the library highly functional and maintainable without over-engineering.URLRequest
, utilizing the definitions in HTTP
.TransportSession
(URLSession
by default) to execute URLRequests
. Deals with raw HTTP
and Data
.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.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.
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))
}
}
Request
Locallylet createPostRequest = Request(method: .post, url: URL(string: "https://jsonplaceholder.typicode.com/posts")!, headers: [.contentType: .applicationJSON],
body: try? HTTP.Body(post))
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
}
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
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...
}
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...
}
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:
Models.swift
, Requests.swift
ViewController.swift
ViewController.swift
InterfaceController.swift
AnyRequest<T>
struct.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.
dependencies: [
.package(url: "https://github.com/BottleRocketStudios/iOS-Hyperspace.git", from: "3.2.1")
]
Hyperspace is available under the Apache 2.0 license. See the LICENSE.txt file for more info.
link |
Stars: 39 |
Last commit: 2 weeks ago |
Make TransportService
thread-safe
Daniel Larsen
#145
Added async / await functions for executing requests Daniel Larsen #142
Migrate to use .xcframework's when resolving depencies using Carthage Will McGinty #139
Add acceptedRange
to HTTP.Status
subtypes and allow for individual subtypes to be detected.
Will McGinty
#139
Strongly type HTTP.Request.headers
and HTTP.Response.headers
as [HTTP.HeaderKey: HTTP.HeaderValue]
.
Will McGinty
#139
Add functionality to consider an arbitrary TransportFailure
a success, using Request.recoveryTransformer
.
Will McGinty
#139
Add an overload to map
which passes along the TransportSuccess
as well as the original Response
.
Will McGinty
#139
Migrate the request recovery strategy to the BackendServiceProtocol definition. Will McGinty #110
Added method
parameter to HTTP.Request
.
Alex Reyes
#138
Changed underlying error in AnyError's NetworkServiceFailureInitializable implementation from NetworkServiceError to NetworkServiceFailure so it can return its failure response rather than nil. Richard Burgess #95
Finished migrating all targets to Swift 5. Tyler Milner #100
Migrate the request recovery strategy to the BackendServiceProtocol definition. Will McGinty #110
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
#117
Rename DecodingFailureInitializable
to DecodingFailureRepresentable
and make the failing HTTP.Response
available during initialization.
Will McGinty
#117
Create an HTTP.Body
type to abstract the Data
of a URLRequest
.
Will McGinty
#117
Several changes to simplify and refine DecodableContainer
, as well as introduce EncodableContainer
and CodableContainer
.
Will McGinty
#117
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
#117
Rename Network*
to Transport*
to provide a clearer distinction between the role of the BackendService
and TransportService
.
Will McGinty
#117
Utilize URLError
as part of the Transporting
protocol to allow for more granularity and detail in error reporting.
Will McGinty
#117
Make TransportError
inits public
.
Earl Gaspard
#121
Create a form URL encoded HTTP.Body
convenience
Will McGinty
#125
Add deprecated typealias to ease migration to 4.0 Will McGinty #124
Create EmptyDecodingStrategy
to add flexibility to decoding EmptyResponse
Will McGinty
#130
Add defaultDecoder
to RequestDefaults
and use when initializing a Request
.
Earl Gaspard
#131
Make transportService
public in BackendService
.
Earl Gaspard
#134
Make DecodingFailure.Context
properties public.
Earl Gaspard
#135
Add defaultMaxRecoveryAttempts
to RequestDefaults
and use for maxRecoveryAttempts
in Request
. This changes the default retries from unlimited to 1.
Earl Gaspard
#137
BackendService
if a GET HTTP request with body data is detected.
Will McGinty
#106Swiftpack is being maintained by Petr Pavlik | @ptrpavlik | @swiftpackco | API | Analytics