SwiftyStoreKit
SwiftyStoreKit copied to clipboard
Crash in ProductsInfoController.retrieveProductsInfo
Platform
- [x] iOS
- [ ] macOS
- [ ] tvOS
In app purchase type
- [ ] Consumable
- [x] Non-consumable
- [ ] Auto-Renewable Subscription
- [ ] Non-Renewing Subscription
Environment
- [x] Sandbox
- [x] Production
Version
0.15.0
Report
Issue summary
sometimes, it crash when it retrieve products info.
1 SwiftyStoreKit 0x101497258 partial apply for closure #1 in InAppProductQueryRequest.performCallback(_:) (InAppProductQueryRequest.swift:76)
2 SwiftyStoreKit 0x1014a0f34 thunk for @escaping @callee_guaranteed () -> () (<compiler-generated>)
3 libdispatch.dylib 0x2320bca38 _dispatch_call_block_and_release + 24
4 libdispatch.dylib 0x2320bd7d4 _dispatch_client_callout + 16
5 libdispatch.dylib 0x23209d9e4 _dispatch_main_queue_callback_4CF$VARIANT$armv81 + 1008
6 CoreFoundation 0x23260dec0 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 12
7 CoreFoundation 0x232608df8 __CFRunLoopRun + 1924
8 CoreFoundation 0x232608354 CFRunLoopRunSpecific + 436
9 GraphicsServices 0x23480879c GSEventRunModal + 104
10 UIKitCore 0x25ebf3b68 UIApplicationMain + 212
11 app. 0x100438400 main (main.m:14)
12 libdyld.dylib 0x2320ce8e0 start + 4```
I have the same issue!
Same here
+1
inflightRequests
should be thread safe
Do you have concrete simple steps to reproduce/simulate this bug?
Hello all! Please check out the latest commit on the develop
branch to see if this issue has been resolved (thanks to @cipolleschi and PR #495).
https://github.com/bizz84/SwiftyStoreKit/commit/70761d29fceb2c0d68a93dd3f26c81db4c0321ea is reverting the https://github.com/bizz84/SwiftyStoreKit/pull/495 So the crash still can accure. Also there are 3 force unwraps! It's bad idea.
Like a workaround you can downgrade to old implementation (like i did):
class ProductsInfoController: NSObject {
struct InAppProductQuery {
let request: InAppProductRequest
var completionHandlers: [InAppProductRequestCallback]
}
// As we can have multiple inflight requests, we store them in a dictionary by product ids
private let inflightRequestsQueue = DispatchQueue(label: "InflightRequestsQueue", attributes: .concurrent)
private var threadInflightRequests: [Set<String>: InAppProductQuery] = [:]
private var inflightRequests: [Set<String>: InAppProductQuery] {
get {
return inflightRequestsQueue.sync {
return threadInflightRequests
}
}
set {
inflightRequestsQueue.async(flags: .barrier) {
self.threadInflightRequests = newValue
}
}
}
func retrieveProductsInfo(_ productIds: Set<String>, completion: @escaping (RetrieveResults) -> Void) {
guard !Thread.current.isMainThread else {
DispatchQueue.global().async { [weak self] in
self?.retrieveProductsInfo(productIds, completion: completion)
}
return
}
if inflightRequests[productIds] == nil {
let request = InAppProductQueryRequest(productIds: productIds) { results in
if let query = self.inflightRequests[productIds] {
for completion in query.completionHandlers {
completion(results)
}
self.inflightRequests[productIds] = nil
} else {
// should not get here, but if it does it seems reasonable to call the outer completion block
completion(results)
}
}
inflightRequests[productIds] = InAppProductQuery(request: request, completionHandlers: [completion])
request.start()
} else {
inflightRequests[productIds]?.completionHandlers.append(completion)
}
}
func cancelAllInflightRequests() {
guard !Thread.current.isMainThread else {
DispatchQueue.global().async { [weak self] in
self?.cancelAllInflightRequests()
}
return
}
inflightRequests.forEach({ $0.value.request.cancel() })
inflightRequests.removeAll()
}
}