From 8be06f6e4967bf25af5d524f1f57c79a2a31569d Mon Sep 17 00:00:00 2001 From: Gunhan Sancar Date: Wed, 22 Mar 2023 10:04:22 +0000 Subject: [PATCH 1/4] feat(Network): Adds retry mechanism for the failed networking requests --- AFNetworkDataSource.swift | 51 -------------- MoyaNetworkDataSource.swift | 19 ------ Pod/Classes/Common/Model/Constants.swift | 6 ++ Pod/Classes/Network/DI/NetworkModule.swift | 4 +- .../MoyaAwesomeAdsApiDataSource.swift | 68 ++++++++++++------- 5 files changed, 53 insertions(+), 95 deletions(-) delete mode 100644 AFNetworkDataSource.swift delete mode 100644 MoyaNetworkDataSource.swift diff --git a/AFNetworkDataSource.swift b/AFNetworkDataSource.swift deleted file mode 100644 index 8770c980a..000000000 --- a/AFNetworkDataSource.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// AFNetworkDataSource.swift -// SuperAwesome -// -// Created by Gunhan Sancar on 08/07/2020. -// - -import Alamofire - -class AFNetworkDataSource: NetworkDataSourceType { - func getData(url: String, completion: @escaping OnResultListener) { - AF.request(url).responseData { response in - if let data = response.data { - completion(Result.success(data)) - } else { - completion(Result.failure(AwesomeAdsError.network)) - } - } - } - - func downloadFile(url: String, completion: @escaping OnResultListener) { - let destination: DownloadRequest.Destination = { _, _ in - let fileName = url.toMD5 - let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] - let fileURL = documentsURL.appendingPathComponent(fileName) - - return (fileURL, [.removePreviousFile, .createIntermediateDirectories]) - } - - AF.download(url, to: destination).response { response in - debugPrint(response) - - if response.error == nil, let path = response.fileURL?.path { - completion(Result.success(path)) - } else { - completion(Result.failure(AwesomeAdsError.network)) - } - } - } - - func clearFiles() { - let fileManager = FileManager.default - let documentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first! - - do { - try fileManager.removeItem(at: documentsURL) - } catch { - return - } - } -} diff --git a/MoyaNetworkDataSource.swift b/MoyaNetworkDataSource.swift deleted file mode 100644 index 273a28efc..000000000 --- a/MoyaNetworkDataSource.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// MoyaNetworkDataSource.swift -// SuperAwesome -// -// Created by Gunhan Sancar on 06/07/2020. -// - -import Alamofire - -class MoyaNetworkDataSource { - func get(_ url: String, completion: @escaping Completion) { - AF.request(url).responseData { response in - switch response.result { - case .success: completion(Result.success(response.value ?? Data())) - case .failure(let error): completion(Result.failure(error)) - } - } - } -} diff --git a/Pod/Classes/Common/Model/Constants.swift b/Pod/Classes/Common/Model/Constants.swift index a3f74dd40..d137df6d0 100644 --- a/Pod/Classes/Common/Model/Constants.swift +++ b/Pod/Classes/Common/Model/Constants.swift @@ -6,6 +6,12 @@ // struct Constants { + /// Number of retries for each network request before it fails + static let numberOfRetries = 5 + + /// The delay between each request when retrying + static let retryDelay: TimeInterval = 1 + static let defaultClickThresholdInSecs = 5 static let defaultTestMode = false static let defaultParentalGate = false diff --git a/Pod/Classes/Network/DI/NetworkModule.swift b/Pod/Classes/Network/DI/NetworkModule.swift index 42d47376b..e4a891d63 100644 --- a/Pod/Classes/Network/DI/NetworkModule.swift +++ b/Pod/Classes/Network/DI/NetworkModule.swift @@ -25,7 +25,9 @@ class NetworkModule: DependencyModule { cont.resolve() as MoyaLoggerPlugin]) } container.single(AwesomeAdsApiDataSourceType.self) { cont, _ in - MoyaAwesomeAdsApiDataSource(provider: cont.resolve(), environment: cont.resolve()) + MoyaAwesomeAdsApiDataSource(provider: cont.resolve(), + environment: cont.resolve(), + retryDelay: Constants.retryDelay) } container.single(NetworkDataSourceType.self) { _, _ in AFNetworkDataSource() diff --git a/Pod/Classes/Network/DataSources/MoyaAwesomeAdsApiDataSource.swift b/Pod/Classes/Network/DataSources/MoyaAwesomeAdsApiDataSource.swift index 4b32fba94..0d5448696 100644 --- a/Pod/Classes/Network/DataSources/MoyaAwesomeAdsApiDataSource.swift +++ b/Pod/Classes/Network/DataSources/MoyaAwesomeAdsApiDataSource.swift @@ -8,18 +8,20 @@ import Moya class MoyaAwesomeAdsApiDataSource: AwesomeAdsApiDataSourceType { - + private let provider: MoyaProvider private let environment: Environment - - init(provider: MoyaProvider, environment: Environment) { + private let retryDelay: TimeInterval + + init(provider: MoyaProvider, environment: Environment, retryDelay: TimeInterval) { self.provider = provider self.environment = environment + self.retryDelay = retryDelay } - + func getAd(placementId: Int, query: QueryBundle, completion: @escaping OnResult) { let target = AwesomeAdsTarget(environment, .ad(placementId: placementId, query: query)) - + provider.request(target) { result in switch result { case .success(let response): @@ -35,7 +37,7 @@ class MoyaAwesomeAdsApiDataSource: AwesomeAdsApiDataSourceType { } } } - + func getAd(placementId: Int, lineItemId: Int, creativeId: Int, @@ -50,7 +52,7 @@ class MoyaAwesomeAdsApiDataSource: AwesomeAdsApiDataSourceType { query: query ) ) - + provider.request(target) { result in switch result { case .success(let response): @@ -66,10 +68,10 @@ class MoyaAwesomeAdsApiDataSource: AwesomeAdsApiDataSourceType { } } } - + func signature(lineItemId: Int, creativeId: Int, completion: @escaping (Result) -> Void) { let target = AwesomeAdsTarget(environment, .signature(lineItemId: lineItemId, creativeId: creativeId)) - + provider.request(target) { result in switch result { case .success(let response): @@ -85,40 +87,58 @@ class MoyaAwesomeAdsApiDataSource: AwesomeAdsApiDataSourceType { } } } - + func impression(query: QueryBundle, completion: OnResult?) { let target = AwesomeAdsTarget(environment, .impression(query: query)) responseHandler(target: target, completion: completion) } - + func click(query: QueryBundle, completion: OnResult?) { let target = AwesomeAdsTarget(environment, .click(query: query)) responseHandler(target: target, completion: completion) } - + func videoClick(query: QueryBundle, completion: OnResult?) { let target = AwesomeAdsTarget(environment, .videoClick(query: query)) responseHandler(target: target, completion: completion) } - + func event(query: QueryBundle, completion: OnResult?) { let target = AwesomeAdsTarget(environment, .event(query: query)) responseHandler(target: target, completion: completion) } - + private func responseHandler(target: AwesomeAdsTarget, completion: OnResult?) { - provider.request(target) { result in - switch result { - case .success(let response): - do { - _ = try response.filterSuccessfulStatusCodes() - completion?(Result.success(Void())) - } catch let error { - completion?(Result.failure(error)) + var retries = 0 + let delay = retryDelay + + func innerRequest() { + provider.request(target) { result in + switch result { + case .success(let response): + do { + _ = try response.filterSuccessfulStatusCodes() + completion?(Result.success(Void())) + } catch let error { + // If the server responds with a 4xx or 5xx error + completion?(Result.failure(error)) + } + case .failure(let error): + // This means there was a network failure + // - either the request wasn't sent (connectivity), + // - or no response was received (server timed out) + if retries < Constants.numberOfRetries { + retries += 1 + DispatchQueue.global().asyncAfter(deadline: .now() + delay) { + innerRequest() + } + } else { + completion?(Result.failure(error)) + } } - case .failure(let error): - completion?(Result.failure(error)) } } + + innerRequest() } } From 0807858be6d1cf57cb1e0a89931711366cc594ba Mon Sep 17 00:00:00 2001 From: Gunhan Sancar Date: Wed, 22 Mar 2023 14:25:37 +0000 Subject: [PATCH 2/4] Apply suggestions from code review Co-authored-by: Myles Eynon --- .../Network/DataSources/MoyaAwesomeAdsApiDataSource.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Pod/Classes/Network/DataSources/MoyaAwesomeAdsApiDataSource.swift b/Pod/Classes/Network/DataSources/MoyaAwesomeAdsApiDataSource.swift index 0d5448696..5ddf3a2ac 100644 --- a/Pod/Classes/Network/DataSources/MoyaAwesomeAdsApiDataSource.swift +++ b/Pod/Classes/Network/DataSources/MoyaAwesomeAdsApiDataSource.swift @@ -113,7 +113,7 @@ class MoyaAwesomeAdsApiDataSource: AwesomeAdsApiDataSourceType { let delay = retryDelay func innerRequest() { - provider.request(target) { result in + provider.request(target) { [weak self] result in switch result { case .success(let response): do { @@ -129,8 +129,8 @@ class MoyaAwesomeAdsApiDataSource: AwesomeAdsApiDataSourceType { // - or no response was received (server timed out) if retries < Constants.numberOfRetries { retries += 1 - DispatchQueue.global().asyncAfter(deadline: .now() + delay) { - innerRequest() + DispatchQueue.global().asyncAfter(deadline: .now() + delay) { [weak self] in + self?.innerRequest() } } else { completion?(Result.failure(error)) From 31b76da849ce933c84f02d518a1801ed2a9edb15 Mon Sep 17 00:00:00 2001 From: Gunhan Sancar Date: Wed, 22 Mar 2023 14:30:22 +0000 Subject: [PATCH 3/4] chore(): fix weak self reference, and apply swiftlint --- Example/Shared/Networking/GetPlacements.swift | 2 +- Pod/Classes/Common/Model/Constants.swift | 4 +-- .../MoyaAwesomeAdsApiDataSource.swift | 36 +++++++++---------- .../Managed/SAManagedAdViewController.swift | 4 +-- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Example/Shared/Networking/GetPlacements.swift b/Example/Shared/Networking/GetPlacements.swift index 79e02d68b..3aead3b5d 100644 --- a/Example/Shared/Networking/GetPlacements.swift +++ b/Example/Shared/Networking/GetPlacements.swift @@ -22,7 +22,7 @@ class GetPlacements { guard let url = URL(string: "\(root)/placements.json") else { return nil } let session = URLSession.shared let publisher = session.dataTaskPublisher(for: url) - .tryMap() { element -> Data in + .tryMap { element -> Data in guard let httpResponse = element.response as? HTTPURLResponse, httpResponse.statusCode == 200 else { throw URLError(.badServerResponse) diff --git a/Pod/Classes/Common/Model/Constants.swift b/Pod/Classes/Common/Model/Constants.swift index d137df6d0..c5cffd60f 100644 --- a/Pod/Classes/Common/Model/Constants.swift +++ b/Pod/Classes/Common/Model/Constants.swift @@ -8,10 +8,10 @@ struct Constants { /// Number of retries for each network request before it fails static let numberOfRetries = 5 - + /// The delay between each request when retrying static let retryDelay: TimeInterval = 1 - + static let defaultClickThresholdInSecs = 5 static let defaultTestMode = false static let defaultParentalGate = false diff --git a/Pod/Classes/Network/DataSources/MoyaAwesomeAdsApiDataSource.swift b/Pod/Classes/Network/DataSources/MoyaAwesomeAdsApiDataSource.swift index 5ddf3a2ac..414334d8d 100644 --- a/Pod/Classes/Network/DataSources/MoyaAwesomeAdsApiDataSource.swift +++ b/Pod/Classes/Network/DataSources/MoyaAwesomeAdsApiDataSource.swift @@ -8,20 +8,20 @@ import Moya class MoyaAwesomeAdsApiDataSource: AwesomeAdsApiDataSourceType { - + private let provider: MoyaProvider private let environment: Environment private let retryDelay: TimeInterval - + init(provider: MoyaProvider, environment: Environment, retryDelay: TimeInterval) { self.provider = provider self.environment = environment self.retryDelay = retryDelay } - + func getAd(placementId: Int, query: QueryBundle, completion: @escaping OnResult) { let target = AwesomeAdsTarget(environment, .ad(placementId: placementId, query: query)) - + provider.request(target) { result in switch result { case .success(let response): @@ -37,7 +37,7 @@ class MoyaAwesomeAdsApiDataSource: AwesomeAdsApiDataSourceType { } } } - + func getAd(placementId: Int, lineItemId: Int, creativeId: Int, @@ -52,7 +52,7 @@ class MoyaAwesomeAdsApiDataSource: AwesomeAdsApiDataSourceType { query: query ) ) - + provider.request(target) { result in switch result { case .success(let response): @@ -68,10 +68,10 @@ class MoyaAwesomeAdsApiDataSource: AwesomeAdsApiDataSourceType { } } } - + func signature(lineItemId: Int, creativeId: Int, completion: @escaping (Result) -> Void) { let target = AwesomeAdsTarget(environment, .signature(lineItemId: lineItemId, creativeId: creativeId)) - + provider.request(target) { result in switch result { case .success(let response): @@ -87,33 +87,33 @@ class MoyaAwesomeAdsApiDataSource: AwesomeAdsApiDataSourceType { } } } - + func impression(query: QueryBundle, completion: OnResult?) { let target = AwesomeAdsTarget(environment, .impression(query: query)) responseHandler(target: target, completion: completion) } - + func click(query: QueryBundle, completion: OnResult?) { let target = AwesomeAdsTarget(environment, .click(query: query)) responseHandler(target: target, completion: completion) } - + func videoClick(query: QueryBundle, completion: OnResult?) { let target = AwesomeAdsTarget(environment, .videoClick(query: query)) responseHandler(target: target, completion: completion) } - + func event(query: QueryBundle, completion: OnResult?) { let target = AwesomeAdsTarget(environment, .event(query: query)) responseHandler(target: target, completion: completion) } - + private func responseHandler(target: AwesomeAdsTarget, completion: OnResult?) { var retries = 0 let delay = retryDelay - + func innerRequest() { - provider.request(target) { [weak self] result in + provider.request(target) { result in switch result { case .success(let response): do { @@ -129,8 +129,8 @@ class MoyaAwesomeAdsApiDataSource: AwesomeAdsApiDataSourceType { // - or no response was received (server timed out) if retries < Constants.numberOfRetries { retries += 1 - DispatchQueue.global().asyncAfter(deadline: .now() + delay) { [weak self] in - self?.innerRequest() + DispatchQueue.global().asyncAfter(deadline: .now() + delay) { + innerRequest() } } else { completion?(Result.failure(error)) @@ -138,7 +138,7 @@ class MoyaAwesomeAdsApiDataSource: AwesomeAdsApiDataSourceType { } } } - + innerRequest() } } diff --git a/Pod/Classes/UI/Managed/SAManagedAdViewController.swift b/Pod/Classes/UI/Managed/SAManagedAdViewController.swift index 451623d35..be247b610 100644 --- a/Pod/Classes/UI/Managed/SAManagedAdViewController.swift +++ b/Pod/Classes/UI/Managed/SAManagedAdViewController.swift @@ -66,7 +66,7 @@ import WebKit private func configureCloseButton() { - if (closeButton != nil) { + if closeButton != nil { closeButton?.removeFromSuperview() closeButton = nil } @@ -161,7 +161,7 @@ extension SAManagedAdViewController: AdViewJavaScriptBridge { func onEvent(event: AdEvent) { callback?(placementId, event) - if (event == .adShown && config.closeButtonState == .visibleWithDelay) { + if event == .adShown && config.closeButtonState == .visibleWithDelay { showCloseButtonAfterDelay() } From 6b60ff789f1bab358df1bfbd02cc744297fd87c3 Mon Sep 17 00:00:00 2001 From: Gunhan Sancar Date: Wed, 22 Mar 2023 16:57:55 +0000 Subject: [PATCH 4/4] chore(): added logger --- Pod/Classes/Network/DI/NetworkModule.swift | 3 ++- .../Network/DataSources/MoyaAwesomeAdsApiDataSource.swift | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Pod/Classes/Network/DI/NetworkModule.swift b/Pod/Classes/Network/DI/NetworkModule.swift index e4a891d63..7166285b5 100644 --- a/Pod/Classes/Network/DI/NetworkModule.swift +++ b/Pod/Classes/Network/DI/NetworkModule.swift @@ -27,7 +27,8 @@ class NetworkModule: DependencyModule { container.single(AwesomeAdsApiDataSourceType.self) { cont, _ in MoyaAwesomeAdsApiDataSource(provider: cont.resolve(), environment: cont.resolve(), - retryDelay: Constants.retryDelay) + retryDelay: Constants.retryDelay, + logger: cont.resolve(param: MoyaAwesomeAdsApiDataSource.self) as LoggerType) } container.single(NetworkDataSourceType.self) { _, _ in AFNetworkDataSource() diff --git a/Pod/Classes/Network/DataSources/MoyaAwesomeAdsApiDataSource.swift b/Pod/Classes/Network/DataSources/MoyaAwesomeAdsApiDataSource.swift index 414334d8d..d9927b518 100644 --- a/Pod/Classes/Network/DataSources/MoyaAwesomeAdsApiDataSource.swift +++ b/Pod/Classes/Network/DataSources/MoyaAwesomeAdsApiDataSource.swift @@ -12,11 +12,13 @@ class MoyaAwesomeAdsApiDataSource: AwesomeAdsApiDataSourceType { private let provider: MoyaProvider private let environment: Environment private let retryDelay: TimeInterval + private let logger: LoggerType - init(provider: MoyaProvider, environment: Environment, retryDelay: TimeInterval) { + init(provider: MoyaProvider, environment: Environment, retryDelay: TimeInterval, logger: LoggerType) { self.provider = provider self.environment = environment self.retryDelay = retryDelay + self.logger = logger } func getAd(placementId: Int, query: QueryBundle, completion: @escaping OnResult) { @@ -111,6 +113,7 @@ class MoyaAwesomeAdsApiDataSource: AwesomeAdsApiDataSourceType { private func responseHandler(target: AwesomeAdsTarget, completion: OnResult?) { var retries = 0 let delay = retryDelay + let innerLogger = logger func innerRequest() { provider.request(target) { result in @@ -128,11 +131,13 @@ class MoyaAwesomeAdsApiDataSource: AwesomeAdsApiDataSourceType { // - either the request wasn't sent (connectivity), // - or no response was received (server timed out) if retries < Constants.numberOfRetries { + innerLogger.error("Network failure, retrying again", error: error) retries += 1 DispatchQueue.global().asyncAfter(deadline: .now() + delay) { innerRequest() } } else { + innerLogger.error("Number of retries reached", error: error) completion?(Result.failure(error)) } }