diff --git a/Sources/SwiftyStoreKit/ProductsInfoController.swift b/Sources/SwiftyStoreKit/ProductsInfoController.swift index e54d065f..32371a03 100644 --- a/Sources/SwiftyStoreKit/ProductsInfoController.swift +++ b/Sources/SwiftyStoreKit/ProductsInfoController.swift @@ -49,7 +49,20 @@ class ProductsInfoController: NSObject { } // As we can have multiple inflight requests, we store them in a dictionary by product ids - private var inflightRequests: [Set: InAppProductQuery] = [:] + private var inflightRequestsStorage: [Set: InAppProductQuery] = [:] + private let requestsQueue = DispatchQueue(label: "inflightRequestsQueue", attributes: .concurrent) + private var inflightRequests: [Set: InAppProductQuery] { + get { + requestsQueue.sync { + inflightRequestsStorage + } + } + set { + requestsQueue.sync(flags: .barrier) { + inflightRequestsStorage = newValue + } + } + } @discardableResult func retrieveProductsInfo(_ productIds: Set, completion: @escaping (RetrieveResults) -> Void) -> InAppProductRequest { diff --git a/SwiftyStoreKit-iOS-Demo/ViewController.swift b/SwiftyStoreKit-iOS-Demo/ViewController.swift index fa640325..a690403a 100644 --- a/SwiftyStoreKit-iOS-Demo/ViewController.swift +++ b/SwiftyStoreKit-iOS-Demo/ViewController.swift @@ -279,6 +279,8 @@ extension ViewController { case .success(let purchase): print("Purchase Success: \(purchase.productId)") return nil + case .deferred(purchase: _): + return alertWithTitle("Purchase deferred", message: "The purchase deferred") case .error(let error): print("Purchase Failed: \(error)") switch error.code { diff --git a/SwiftyStoreKit-macOS-Demo/ViewController.swift b/SwiftyStoreKit-macOS-Demo/ViewController.swift index 071c6d9f..d0eaf729 100644 --- a/SwiftyStoreKit-macOS-Demo/ViewController.swift +++ b/SwiftyStoreKit-macOS-Demo/ViewController.swift @@ -193,6 +193,8 @@ extension ViewController { case .success(let purchase): print("Purchase Success: \(purchase.productId)") return alertWithTitle("Thank You", message: "Purchase completed") + case .deferred(purchase: _): + return alertWithTitle("Purchase deferred", message: "The purchase deferred") case .error(let error): print("Purchase Failed: \(error)") switch error.code { diff --git a/Tests/SwiftyStoreKitTests/ProductsInfoControllerTests.swift b/Tests/SwiftyStoreKitTests/ProductsInfoControllerTests.swift index 3343be52..99ccc26f 100644 --- a/Tests/SwiftyStoreKitTests/ProductsInfoControllerTests.swift +++ b/Tests/SwiftyStoreKitTests/ProductsInfoControllerTests.swift @@ -54,11 +54,21 @@ class TestInAppProductRequest: InAppProductRequest { class TestInAppProductRequestBuilder: InAppProductRequestBuilder { - var requests: [ TestInAppProductRequest ] = [] + private var _requests: [TestInAppProductRequest] = [] + private let requestsQueue = DispatchQueue(label: "builderRequestsQueue", attributes: .concurrent) + var requests: [ TestInAppProductRequest ] { + get { + requestsQueue.sync { + _requests + } + } + } func request(productIds: Set, callback: @escaping InAppProductRequestCallback) -> InAppProductRequest { let request = TestInAppProductRequest(productIds: productIds, callback: callback) - requests.append(request) + requestsQueue.sync(flags: .barrier) { + _requests.append(request) + } return request } @@ -66,7 +76,7 @@ class TestInAppProductRequestBuilder: InAppProductRequestBuilder { requests.forEach { $0.fireCallback() } - requests = [] + _requests = [] } } @@ -143,25 +153,28 @@ class ProductsInfoControllerTests: XCTestCase { // Create the expectation not to let the test finishes before the other threads complete let expectation = XCTestExpectation(description: "Expect downloads of product informations") - // Create the dispatch group to let the test verifies the assert only when + // Create the dispatch groups to let the test verifies the assert only when // everything else finishes. - let group = DispatchGroup() + let groupCompletion = DispatchGroup() + let groupFire = DispatchGroup() // Dispatch a request for every product in a different thread for product in testProducts { + groupCompletion.enter() + groupFire.enter() DispatchQueue.global().async { - group.enter() productInfoController.retrieveProductsInfo([product]) { _ in completionCallbackCount += 1 - group.leave() + groupCompletion.leave() } + groupFire.leave() } } - DispatchQueue.global().asyncAfter(deadline: .now()+0.1) { + groupFire.notify(queue: DispatchQueue.global()) { requestBuilder.fireCallbacks() } // Fullfil the expectation when every thread finishes - group.notify(queue: DispatchQueue.global()) { + groupCompletion.notify(queue: DispatchQueue.global()) { XCTAssertEqual(completionCallbackCount, self.testProducts.count) expectation.fulfill()