Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix concurrent retrieve product request #675

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 14 additions & 1 deletion Sources/SwiftyStoreKit/ProductsInfoController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>: InAppProductQuery] = [:]
private var _inflightRequests: [Set<String>: InAppProductQuery] = [:]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_ is a rudiment from obj_c. rename please without underscore.

private let requestsQueue = DispatchQueue(label: "inflightRequestsQueue", attributes: .concurrent)
private var inflightRequests: [Set<String>: InAppProductQuery] {
get {
requestsQueue.sync {
_inflightRequests
}
}
set {
requestsQueue.sync(flags: .barrier) {
self._inflightRequests = newValue
}
}
}

@discardableResult
func retrieveProductsInfo(_ productIds: Set<String>, completion: @escaping (RetrieveResults) -> Void) -> InAppProductRequest {
Expand Down
2 changes: 2 additions & 0 deletions SwiftyStoreKit-iOS-Demo/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions SwiftyStoreKit-macOS-Demo/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
31 changes: 22 additions & 9 deletions Tests/SwiftyStoreKitTests/ProductsInfoControllerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,19 +54,29 @@ 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<String>, callback: @escaping InAppProductRequestCallback) -> InAppProductRequest {
let request = TestInAppProductRequest(productIds: productIds, callback: callback)
requests.append(request)
requestsQueue.sync(flags: .barrier) {
_requests.append(request)
}
return request
}

func fireCallbacks() {
requests.forEach {
$0.fireCallback()
}
requests = []
_requests = []
}
}

Expand Down Expand Up @@ -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()
Expand Down