Packer is a networking library that makes your requests "plug and play". Easy!
github "stone-payments/packer"
pod 'Packer'
Every request that one specific API has will need to be represented by a class/struct that implements the APIRequest
protocol. It will require to specific a typealias
for Response indicating what is expected to be the response for this request. You will also need to implement a property resourceName
containing the endpoint for this request in the API. It's good to use a read-only computed property for this so that the Codable
will not consider this property in the serialization.
The Response's type must implement the Decodable
protocol. And any property in the request tree must implement the Encodable
protocol.
struct FooRequest: APIRequest {
// this is the type of the response for this request
typealias Response = FooResponse
// the string containing the endpoing should be a computed property
var resourceName: String {
return "fooEndpoint"
}
var foo: String
var bar: Int
}
struct FooResponse: Decodable {
var status: Bool
var message: String
}
struct FooRequest: APIRequest {
typealias Response = FooResponse
// build the URL using the ID property
var resourceName: String {
return "foo/\(fooId)"
}
// then make the property for the ID private so it will be ignored in the serialization
private var fooId: String
}
For each API you consume in your app you will need to implement the protocol APIClient
.
Initialize the baseUrl
property with a string having the base URL for the API. The property session
must be a URLSession
and the dataTask
a URLSessionDataTask
. And finally, implement the function send
.
class FooAPI: APIClient {
// we initialize with a default configuration
var session = URLSession(configuration: .default)
// here we only declare it
var dataTask: URLSessionDataTask?
// the base URL for the API
var baseUrl = "https://www.foobar.com/v1/"
var headers: [String : String] = ["Authorization":"API-TOKEN"]
// this is how we implement the 'send' function for this example API
func send<T>(_ request: T, method: HTTPMethod, completion: @escaping (Result<T.Response>) -> Void) where T : APIRequest {
do {
let urlRequest = try method.urlRequest(urlString: baseUrl, request: request, headers: headers)
dataTask = session.dataTask(with: urlRequest) { data, response, error in
if let data = data {
do {
if let response = response as? HTTPURLResponse,
(200...204).contains(response.statusCode) { // specify this according to the API
let objectResponse = try JSONDecoder().decode(T.Response.self, from: data)
completion(.success(objectResponse))
}
else { // we are considering everything that's not 200 as an error
completion(.failure(APIError.server(errors: ["Error"], message: "API error.")))
}
} catch {
completion(.failure(error))
}
}
else if let error = error {
completion(.failure(error))
}
else {
completion(.failure(APIError.server(errors: ["Error"], message: nil)))
}
}
dataTask?.resume()
}
catch {
completion(.failure(error))
}
}
}
Note that the HTTPMethod is an Enum and has a function called
urlRequest
that can be used to build theURLRequest
that will be used in theDataTask
.
let fooRequest = FooRequest(foo: "Test", bar: 42)
let fooClient = FooAPI()
fooClient.send(fooRequest, method: .post) { result in
switch result {
case .success(let response):
print(response.message)
case .failure(let error):
print(error)
}
}