Created
October 25, 2020 23:47
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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() | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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