From f09de006f413ce4d230fb05ba46afa2740ed0b05 Mon Sep 17 00:00:00 2001 From: Denis Obukhov Date: Mon, 25 Oct 2021 16:27:35 +0700 Subject: [PATCH] Add the ability to retry a request --- .../Calls/NetworkingClient+Requests.swift | 24 ++++++++++++---- Sources/Networking/NetworkingClient.swift | 4 +-- Sources/Networking/NetworkingRequest.swift | 28 ++++++++++++++++--- 3 files changed, 44 insertions(+), 12 deletions(-) diff --git a/Sources/Networking/Calls/NetworkingClient+Requests.swift b/Sources/Networking/Calls/NetworkingClient+Requests.swift index 7486221..6d0428f 100644 --- a/Sources/Networking/Calls/NetworkingClient+Requests.swift +++ b/Sources/Networking/Calls/NetworkingClient+Requests.swift @@ -32,15 +32,27 @@ public extension NetworkingClient { internal func request(_ httpVerb: HTTPVerb, _ route: String, params: Params = Params()) -> NetworkingRequest { let req = NetworkingRequest() - req.baseURL = baseURL - req.logLevels = logLevels - req.headers = headers req.httpVerb = httpVerb req.route = route req.params = params - req.parameterEncoding = parameterEncoding - req.sessionConfiguration = sessionConfiguration - req.timeout = timeout + + let updateRequest = { [weak req, weak self] in + guard let self = self else { return } + req?.baseURL = self.baseURL + req?.logLevels = self.logLevels + req?.headers = self.headers + req?.parameterEncoding = self.parameterEncoding + req?.sessionConfiguration = self.sessionConfiguration + req?.timeout = self.timeout + } + updateRequest() + req.requestRetrier = { [weak self] in + self?.requestRetrier?($0, $1)? + .handleEvents(receiveOutput: { _ in + updateRequest() + }) + .eraseToAnyPublisher() + } return req } } diff --git a/Sources/Networking/NetworkingClient.swift b/Sources/Networking/NetworkingClient.swift index 010182d..3050459 100644 --- a/Sources/Networking/NetworkingClient.swift +++ b/Sources/Networking/NetworkingClient.swift @@ -1,8 +1,7 @@ import Foundation import Combine -public struct NetworkingClient { - +public class NetworkingClient { /** Instead of using the same keypath for every call eg: "collection", this enables to use a default keypath for parsing collections. @@ -15,6 +14,7 @@ public struct NetworkingClient { public var parameterEncoding = ParameterEncoding.urlEncoded public var timeout: TimeInterval? public var sessionConfiguration = URLSessionConfiguration.default + public var requestRetrier: NetworkRequestRetrier? /** Prints network calls to the console. diff --git a/Sources/Networking/NetworkingRequest.swift b/Sources/Networking/NetworkingRequest.swift index 8fce024..2a9c06f 100644 --- a/Sources/Networking/NetworkingRequest.swift +++ b/Sources/Networking/NetworkingRequest.swift @@ -8,6 +8,8 @@ import Foundation import Combine +public typealias NetworkRequestRetrier = (_ request: URLRequest, _ error: Error) -> AnyPublisher? + public class NetworkingRequest: NSObject { var parameterEncoding = ParameterEncoding.urlEncoded @@ -25,7 +27,9 @@ public class NetworkingRequest: NSObject { var timeout: TimeInterval? let progressPublisher = PassthroughSubject() var sessionConfiguration: URLSessionConfiguration? - + var requestRetrier: NetworkRequestRetrier? + private let maxRetryCount = 3 + public func uploadPublisher() -> AnyPublisher<(Data?, Progress), Error> { guard let urlRequest = buildURLRequest() else { @@ -63,9 +67,12 @@ public class NetworkingRequest: NSObject { return Publishers.Merge(callPublisher, progressPublisher2) .receive(on: DispatchQueue.main).eraseToAnyPublisher() } - + public func publisher() -> AnyPublisher { - + publisher(retryCount: maxRetryCount) + } + + private func publisher(retryCount: Int) -> AnyPublisher { guard let urlRequest = buildURLRequest() else { return Fail(error: NetworkingError.unableToParseRequest as Error) .eraseToAnyPublisher() @@ -87,7 +94,20 @@ public class NetworkingRequest: NSObject { } } return data - }.mapError { error -> NetworkingError in + }.tryCatch({ [weak self, urlRequest] error -> AnyPublisher in + guard + let self = self, + retryCount > 1, + let retryPublisher = self.requestRetrier?(urlRequest, error) + else { + throw error + } + return retryPublisher + .flatMap { _ -> AnyPublisher in + self.publisher(retryCount: retryCount - 1) + } + .eraseToAnyPublisher() + }).mapError { error -> NetworkingError in return NetworkingError(error: error) }.receive(on: DispatchQueue.main).eraseToAnyPublisher() }