Hi everybody!
I'm desperately looking for help as I'm stuck with a rather fundamental problem regarding StoreKit2 - and maybe Swift Concurrency in general:
While renovating several freemium apps I'd like to move from local receipt validation with Receigen / OpenSSL to StoreKit2. These apps are using a dedicated "StoreManager" class which is encapsulating all App Store related operations like fetching products, performing purchases and listening on updates. For this purpose the StoreManager holds an array property with IDs of all purchased products, which is checked when a user invokes a premium function. This array can have various states during the app's life cycle:
- Immediately after app launch (before the receipt / entitlements are checked) the array is empty
- After checking the receipt the array holds all (locally registered) purchases
- Later on it might change if an "Ask to Buy" purchase was approved or a purchase was performed
It is important that the array is instantly used in other (Objective-C) classes to reflect the "point in time" state of purchased products - basically acting like a cache: No async calls, completion handler, notification observer etc.
When moving to StoreKit2 the same logic applies, but the relevant API calls are (of course) in asynchronous functions: Transaction.updates
triggers Transaction.currentEntitlements,
which needs to update the array property. But Xcode 16 is raising a strict error because of potential data races when accessing the instance variable from an asynchronous function / actor.
What is the way to propagate IDs of purchased products app-wide without requiring every calling function as asynchronous? I'm sure I'm missing a general point with Swift Concurrency: Every example I found was working with call-backs / await, and although this talk of WWDC 2021 is addressing "protecting mutable states" I couldn't apply its outcomes to my problem. What am I missing?
Problem solved!
I created a small sample project with a similar construction which just worked: Even async functions could access the class property, while my StoreManager received the following error:
Passing closure as a 'sending' parameter risks causing data races between code in the current task and concurrent execution of the closure
.
By defining StoreManager class as a @MainActor
, access to the mentioned property is possible across threads. All asynchronous / delaying work is done in threads, so I'm fine with forcing the StoreManager to the main thread, no performance impact can be observed.
I assume (!) my sample code was just working as its functions were located in a UIViewController, residing on the main thread anyway. But that's only an (educated) guess...