I have 2 methods I need to call, the second method must be executed using the result of the first method and the second method also returns a value.
I have put together a simple playground that demonstrates a simple version of the flow
import UIKit
protocol TokenLoader {
func load(_ key: String, completion: @escaping (String?) -> Void)
}
protocol Client {
func dispatch(_ request: URLRequest, completion: @escaping (Result<(Data, HTTPURLResponse), Error>) -> Void) -> URLSessionTask
}
class AuthTokenLoader: TokenLoader {
func load(_ key: String, completion: @escaping (String?) -> Void) {
print("was called")
completion("some.access.token")
}
}
class Networking: Client {
private let loader: TokenLoader
init(loader: TokenLoader) {
self.loader = loader
}
func dispatch(_ request: URLRequest, completion: @escaping (Result<(Data, HTTPURLResponse), Error>) -> Void) -> URLSessionTask {
let task = URLSession.shared.dataTask(with: request, completionHandler: { data, response, error in
if let error = error {
completion(.failure(error))
} else if let data = data, let response = response as? HTTPURLResponse {
completion(.success((data, response)))
}
})
task.resume()
return task
}
}
let loader = AuthTokenLoader()
let client = Networking(loader: loader)
let request = URL(string: "https://jsonplaceholder.typicode.com/todos/1")!
client.dispatch(.init(url: request), completion: { print($0) })
I need to use the token returned by AuthTokenLoader
as a header on the request sent by dispatch
method in my Networking
class.
Networking
also returns a task so this request can be cancelled.
As I cannot return from inside the completion block of the AuthTokenLoader
load completion, I unsure how to achieve this.
You can create a wrapper for your task and return that instead.
protocol Task {
func cancel()
}
class URLSessionTaskWrapper: Task {
private var completion: ((Result<(Data, HTTPURLResponse), Error>) -> Void)?
var wrapped: URLSessionTask?
init(_ completion: @escaping (Result<(Data, HTTPURLResponse), Error>) -> Void) {
self.completion = completion
}
func complete(with result: Result<(Data, HTTPURLResponse), Error>) {
completion?(result)
}
func cancel() {
preventFurtherCompletions()
wrapped?.cancel()
}
private func preventFurtherCompletions() {
completion = nil
}
}
Your entire playground would become
protocol TokenLoader {
func load(_ key: String, completion: @escaping (String?) -> Void)
}
protocol Client {
func dispatch(_ request: URLRequest, completion: @escaping (Result<(Data, HTTPURLResponse), Error>) -> Void) -> Task
}
class AuthTokenLoader: TokenLoader {
func load(_ key: String, completion: @escaping (String?) -> Void) {
print("was called")
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
completion("some.access.token")
}
}
}
protocol Task {
func cancel()
}
class URLSessionTaskWrapper: Task {
private var completion: ((Result<(Data, HTTPURLResponse), Error>) -> Void)?
var wrapped: URLSessionTask?
init(_ completion: @escaping (Result<(Data, HTTPURLResponse), Error>) -> Void) {
self.completion = completion
}
func complete(with result: Result<(Data, HTTPURLResponse), Error>) {
completion?(result)
}
func cancel() {
preventFurtherCompletions()
wrapped?.cancel()
}
private func preventFurtherCompletions() {
completion = nil
}
}
class Networking: Client {
private let loader: TokenLoader
init(loader: TokenLoader) {
self.loader = loader
}
func dispatch(_ request: URLRequest, completion: @escaping (Result<(Data, HTTPURLResponse), Error>) -> Void) -> Task {
let task = URLSessionTaskWrapper(completion)
loader.load("token") { token in
task.wrapped = URLSession.shared.dataTask(with: request, completionHandler: { data, response, error in
if let error = error {
task.complete(with: .failure(error))
} else if let data = data, let response = response as? HTTPURLResponse {
task.complete(with: .success((data, response)))
}
})
task.wrapped?.resume()
}
return task
}
}
let loader = AuthTokenLoader()
let client = Networking(loader: loader)
let request = URL(string: "https://jsonplaceholder.typicode.com/todos/1")!
client.dispatch(.init(url: request), completion: { print($0) })