Skip to content

Instantly share code, notes, and snippets.

@fmo91
Created October 25, 2020 23:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fmo91/8fcdd35f6498da423fa992e60c3c874c to your computer and use it in GitHub Desktop.
Save fmo91/8fcdd35f6498da423fa992e60c3c874c to your computer and use it in GitHub Desktop.
Basic support for async operations in Swift based on Combine, using generics, protocol oriented programming and type erasure.
//
// AsyncOperation.swift
// SwiftUIPoC
//
// Created by Fernando Martín Ortiz on 25/10/2020.
//
import Combine
protocol AsyncOperation {
associatedtype Request
associatedtype Response
func execute(_ request: Request) -> AnyPublisher<Response, Error>
}
struct AnyAsyncOperation<Request, Response>: AsyncOperation {
typealias Operation = (Request) -> AnyPublisher<Response, Error>
private let operation: Operation
init(operation: @escaping Operation) {
self.operation = operation
}
func execute(_ request: Request) -> AnyPublisher<Response, Error> {
return operation(request)
}
}
extension AsyncOperation {
func eraseToAnyOperation() -> AnyAsyncOperation<Request, Response> {
return AnyAsyncOperation<Request, Response>(operation: self.execute)
}
}
import Foundation
struct GetUsersRequest: NetworkRequest {
var path: String {
return "https://jsonplaceholder.typicode.com/users"
}
}
typealias GetUsersOperation = NetworkOperation<GetUsersRequest, [User]>
struct User: Codable, Identifiable {
let id: Int
let name: String
}
// ---
GetUsersOperation()
.execute(GetUsersRequest())
.sink(
receiveCompletion: { _ in },
receiveValue: { users in
print(users)
}
)
.store(in: &cancellables)
//
// NetworkOperation.swift
// SwiftUIPoC
//
// Created by Fernando Martín Ortiz on 25/10/2020.
//
import Foundation
import Combine
struct NetworkOperation<Request, Response>: AsyncOperation where Request: NetworkRequest, Response: Codable {
enum NetworkOperationError: Error {
case invalidRequest
}
func execute(_ request: Request) -> AnyPublisher<Response, Error> {
guard let urlRequest = request.urlRequest else {
return Fail(error: NetworkOperationError.invalidRequest)
.eraseToAnyPublisher()
}
return URLSession.shared
.dataTaskPublisher(for: urlRequest)
.map(\.data)
.decode(type: Response.self, decoder: JSONDecoder())
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
}
//
// NetworkRequest.swift
// SwiftUIPoC
//
// Created by Fernando Martín Ortiz on 25/10/2020.
//
import Foundation
protocol NetworkRequest {
var path: String { get }
var method: NetworkMethod { get }
var headers: [String: String]? { get }
var parameters: [String: Any?]? { get }
}
extension NetworkRequest {
var method: NetworkMethod { .get }
var headers: [String: String]? { nil }
var parameters: [String: Any?]? { nil }
var url: URL? {
var components = URLComponents()
var urlString = path
if !isJSONBody && parameters?.isEmpty == false {
components.queryItems = []
for (key, value) in parameters ?? [:] where value is String {
components.queryItems?.append(
URLQueryItem(
name: key,
value: value as? String
)
)
}
urlString += "?\(components.query ?? "")"
}
return URL(string: urlString)
}
private var isJSONBody: Bool {
method != .get && method != .delete
}
var urlRequest: URLRequest? {
guard let url = self.url else {
return nil
}
var request = URLRequest(url: url)
request.allHTTPHeaderFields = headers ?? [:]
request.httpMethod = method.rawValue
let parameters = self.parameters ?? [:]
if isJSONBody {
request.allHTTPHeaderFields?["Content-Type"] = "application/json"
request.httpBody = try? JSONSerialization.data(withJSONObject: parameters, options: [])
}
return request
}
}
enum NetworkMethod: String {
case post = "POST"
case get = "GET"
case put = "PUT"
case delete = "DELETE"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment