HPNetwork

2.0.0

A protocol-based networking stack written in Swift
henrik-dmg/HPNetwork

What's New

Throwing URL Construction

2021-03-24T09:34:10Z

This release is not really a v2 release, but it brings breaking changes. The url: URL? property on NetworkRequest has been removed and replaced by makeURL() throws -> URL

HPNetwork

Swift

HPNetwork is a protocol-based networking stack written in pure Swift

Posting Request

To submit a request, you can use the singleton:

Network.shared.schedule(request) { result in
    switch result {
    case .success(let output):
        // handle result
    case .failure(let error):
        // handle error
    }
}

of with the convenience method:

request.schedule { result in
	// Handle result
}

The result is Result<Request.Output, Error> where Request.Output is inferred from the request object. Network.shared will do its networking on “com.henrikpanhans.Network” (which is a concurrent queue). If you want to use a custom queue, you can pass it in the initialiser:

let customQueue = DispatchQueue(label: "com.henrikpanhans.CustomQueue", qos: .userInitiated, attributes: .concurrent)
let network = Network(queue: customQueue)

You can limit the maximum number of concurrent requests to be executed by setting Network.shared.maximumConcurrentRequests = 5 for example

Combine

You can also call dataTaskPublisher() on any NetworkRequest instance to get a AnyPublisher<Request.Output, Error. The publisher will walk through the same validation and error handling process as the regular Network.

Progress Callback

You can pass a progress handler block to Network like this:

Network.shared.schedule(request: request) { progress in
    print(progress.fractionComplete)
} completion: { result in
    // Result handling as usual
}

Synchronous Requests

If you do stuff like writing CLIs with Swift or need to do synchronous networking for any reason, you can use scheduleSynchronously(...) which returns the same Result<NetworkRequest.Output, Error> as in the closure of the regular method call. There's also a convenience method for NetworkRequest directly which you can call by request.scheduleSynchronously(...)

Cancelling Requests

Any call to schedule(request) { result in ... } returns an instance of NetworkTask that you can cancel by calling task.cancel()

Creating Requests

Basics

HPNetwork is following a rather protocol based approach, so any type that conforms to NetworkRequest can be scheduled as a request. In the most simple terms, that means you supply a URL and a request method.

Example 1:

struct BasicDataRequest: NetworkRequest {

    typealias Output = Data

    var url: URL? {
        // construct your URL here
    }

    var requestMethod: NetworkRequestMethod {
        .get
    }

}

Example 2:

struct BasicDataRequest: NetworkRequest {

    typealias Output = Data

    let url: URL?
    let requestMethod: NetworkRequestMethod

}

let basicRequest = BasicDataRequest(
    url: URL(string: "https://panhans.dev/"),
    requestMethod: .get
)

JSON

If you're working with JSON, you can also use DecodableRequest which requires a JSONDecoder to be supplied. The request will use that decoder to automatically convert the received data to the specified output type

Example 3:

struct BasicDecodableRequest<Output: Decodable>: DecodableRequest {

    let url: URL?
    let requestMethod: NetworkRequestMethod

    var decoder: JSONDecoder {
        JSONDecoder() // use default or custom decoder
    }

}

Intercepting Errors

By default, instances of NetworkRequest will simply forward any encountered errors to the completion block. If you want to do some custom error conversion based on the raw Data that was received, you can implement func convertError(_ error: Error, data: Data?, response: URLResponse?) -> Error in your request model.

URLBuilder

There's a type available that you can use to construct URL instances. To use it, initialise URLBuilder with a host: URLBuilder(host: "apple.com"). You can then add path components and query items in a type-safe way and URLBuilder will automatically take care of formatting and encoding.

URLBuilder(host: "api.openweathermap.org")
    .addingPathComponent("data")
    .addingPathComponent("2.5")
    .addingPathComponent("onecall")
    .addingQueryItem(name: "lat", value: 48.123123012, digits: 5)
    .addingQueryItem(name: "lon", value: -12.9123001299, digits: 5)
    .addingQueryItem(name: "appid", value: "apiKey")
    .addingQueryItem(name: "units", value: "metric")
    .build()

Anything that conforms to QueryStringConvertible can be used directly with the builder. Many Foundation types already conform to it.

Request Authentication

To add authentication to a request, simply supply a authentication: NetworkRequestAuthentication? instance to your request. NetworkRequestAuthentication is an enum and supports basic authentication with a username and password, bearer token authorisation or a raw option if you want full control.

Everything else

Things like httpBody: Data? and headerFields: [NetworkRequestHeaderField] should be pretty self-explanatory so I'm gonna let you figure those out on your own.

WIP

  • Cancellation support
  • Progress callback
  • Improving the documentation
  • Add async variants for the new Swift version
  • Cookie support

Description

  • Swift Tools 5.1.0
View More Packages from this Author

Dependencies

  • None
Last updated: Wed Mar 20 2024 12:22:37 GMT-0900 (Hawaii-Aleutian Daylight Time)