I don't use Swift much anymore, but I need to write some adapter code to call Apple's StoreKit from another language. I thought I would use an actor with async methods, since that seems to place nice with the StoreKit API. But how do I jump from global non-concurrency-aware extern "C"
world, to the actor?
var storeHelp: StoreHelper? = nil // error: not concurrency-safe
@_cdecl("create_store_help_actor")
func create_store_help_actor() {
storeHelp = StoreHelper()
}
actor StoreHelper {
var products: [String: Product] = [:]
func requestProducts(ids: [String]) async { ... }
func listenForTxns() async { ... }
...
}
@_cdecl("listen_for_txns")
func listen_for_txns() {
// How to get actor here?
According to AI, I can do this:
final class Context: @unchecked Sendable {
static let shared = Context()
let storeHelp = StoreHelper()
}
func startUsingIt() {
Task {
await Context.shared.storeHelp.foo()
}
}
I'll do that for now until I learn something better.
I might suggest a very minor refinement, namely make init
private
:
final class Context: @unchecked Sendable {
static let shared = Context()
let storeHelp = StoreHelper()
private init() { }
}
That will avoid ever accidentally invoking Context()
in your code. It doesn’t make too much difference in this example, but it’s the standard defensive-programming of a singleton.
The other approach is to retire Context
altogether:
actor StoreHelper {
static let shared = StoreHelper()
private init() { }
func foo() async {…}
}
And then your function is just:
func startUsingIt() {
Task {
await StoreHelper.shared.foo()
}
}
2 Likes
You may consider using a completion handler to indicate when the task is completed and when startUsingInit()
truly completes vs. just when the task is created.
func startDoingIt(completion: @escaping () -> Void) {
Task {
await StoreHelper.shared.foo()
completion()
}
}