Melatonin (formerly SwiftyNetworking) library is a networking library written in Swift that provides a protocol-oriented approach to load network requests. It provides a protocol Endpoint
to ensure that networking requests are parsed in a generic and type-safe way. Earlier changes can be tracked in SwiftyNetworking library which is no longer updated.
Conformance to Endpoint
protocol is easy and straighforward. This is how the protocol body looks like:
public protocol Endpoint {
associatedtype Response
var scheme: Scheme { get }
var host: String { get }
var port: Int? { get }
var path: String { get }
var method: HTTPMethod { get }
var queries: [URLQuery] { get }
var headers: [HTTPHeader] { get }
func prepare(request: inout URLRequest)
func parse(data: Data, urlResponse: URLResponse) throws -> Response
}
The library includes default implementations for some of the required variables and functions for convenience.
Any object conforming to Endpoint
will automatically get url
and request
properites which will be used by URLSession
to load the request.
You can implement the prepare(request:) method if you need to modify the request before it is loaded.
The @Query
property wrapper is used to declare any property that is a URL query. All properties declared with @Query
inside your endpoint's body will be added to the final url.
struct APIEndpoint: Endpoint {
...
@Query(name: "name") var name: String? = "the-braveknight"
@Query(name: "age") var pageNumber: Int? = 2
...
}
In the above code, the url query will look like this: ?name=the-braveknight&pageNumber=2
. You can still add multiple queries by directly setting the queries
property of your endpoint.
Similarly, the @Header
property wrapper is used to declare headers, which will be added the URLRequest
before it's loaded. The library contains multiple commonly used HTTP headers and you can also implement your own.
struct APIEndpoint: Endpoint {
...
@Header(\.accept) var accept: MIMEType = .json
@Header(\.contentType) var contentType: MIMEType = .json
...
}
Again, you can still add multiple headers at once by directly setting the headers
property of your endpoint.
These result builders allow you to build arrays of query items and headers respectively. For example, we can use them to build our endpoint's queries
and headers
arrays:
struct APIEndpoint: Endpoint {
...
@HeaderGroup var headers: [HTTPHeader] {
Accept(.json)
ContentType(.json)
}
@QueryGroup var queries: [URLQuery] {
Query(name: "name", value: "the-braveknight")
Query(name: "age", value: 2)
}
...
}
In certain cases, for example when the Response
conforms to Decodable
and we expect to decode JSON, it would be reasonable to provide default implementation for parse(data:urlResponse:) method to handle that automatically.
public extension Endpoint where Response : Decodable {
func parse(data: Data, urlResponse: URLResponse) throws -> Response {
let decoder = JSONDecoder()
return try decoder.decode(Response.self, from: data)
}
}
You can still provide your own implementation of this method to override this implementation.
This is an example endpoint with GET
method to parse requests from Agify.io API.
The response body from an API call (https://api.agify.io/?name=bella) looks like this:
{
"name" : "bella",
"age" : 34,
"count" : 40138
}
A custom Swift struct that can contain this data would look like this:
struct Person : Decodable {
let name: String
let age: Int
}
Finally, here is how our endpoint will look like:
struct AgifyAPIEndpoint : Endpoint {
typealias Response = Person
let host: String = "api.agify.io"
let path: String = "/"
@Query(name: "name") var name: String? = nil
@Header(\.accept) var accept: MIMEType = .json
}
We could use the Swift dot syntax to make it more convenient to call our endpoint.
extension Endpoint where Self == AgifyAPIEndpoint {
static func estimatedAge(forName personName: String) -> Self {
AgifyAPIEndpoint(name: personName)
}
}
Finally, this is how we would call our endpoint. The result is of type Result<Person, Error>
.
URLSession.shared.load(.estimatedAge(forName: "Zaid")) { result in
do {
let person = try result.get()
print("\(person.name) is probably \(person.age) years old.")
} catch {
// Handle errors
}
}
Melatonin supports loading endpoints using Combine
framework.
let subscription: AnyCancellable = URLSession.shared.load(.estimatedAge(forName: "Zaid"))
.sink { completion in
// Handle errors
} receiveValue: { person in
print("\(person.name) is probably \(person.age) years old.")
}
Melatonin also supports loading an endpoint using Swift Concurrency and async/await
.
Task {
do {
let person = try await URLSession.shared.load(.estimatedAge(forName: "Zaid"))
print("\(person.name) is probably \(person.age) years old.")
} catch {
// Handle errors
}
}
link |
Stars: 0 |
Last commit: 2 weeks ago |
Swiftpack is being maintained by Petr Pavlik | @ptrpavlik | @swiftpackco | API | Analytics