AppTransaction: how to use in ObjC apps (now that we are forced to use it after the exit(173) deprecation)

Hello

We are developers of a long-running game series and now reports have started to come in that users who install any of our previous games from the Mac App Store on OS X Sequoia are shown a popup claiming "The exit(173) API is no longer available". It's actually a lie, the mechanism is still there, the receipt generation still works and the game still runs afterwards. But the popup is confusing to users therefore we need to update the code.

Apparently the replacement for the old receipt generation mechanism is AppTransaction which does not exist for Objective C. We have attempted to use it using the Swift/ObjC interoperability and failed so far. The problem is that we need to call async methods in AppTransaction and all our attempts to make this work have failed so far. It seems as the actor/@MainActor concept is not supported by Swift/ObjC interoperability and without those concepts we don't know how to pass results from the async context to the callers from ObjC.

The lack of usable information and code online regarding this topic is highly frustrating. Apple really needs to provide better support for developers if they want us to continue to support the Mac platform with high quality games and applications on the Mac App Store.

We would appreciate if anyone can cook up a working sample code how to use AppTransaction in ObjC. Thanks in advance!

we need to call async methods in AppTransaction

The interop converts async Swift functions into objC functions with completion handler callbacks. For example, this Swift method:

@objc static func foo() async

Can be called from objC as

[ClassName fooWithCompletionHandler: ^{
  ...
}];

(Or something like that.)

I tried that and it compiles but I get a crash exactly where the Swift function attempts to call the completion handler:

0x10a7f36cd <+93>:  callq  0x10a7f3920               ; Swift._runTaskForBridgedAsyncMethod(__owned @Sendable () async -> ()) -> () at <compiler-generated>
#1	0x00007ff814974f85 in pthread_kill ()
#2	0x00007ff814895b19 in abort ()
#3	0x00007ff82623dc21 in swift::fatalErrorv ()
#4	0x00007ff82623dcab in swift::fatalError ()
#5	0x00007ff826270746 in swift::ResolveAsSymbolicReference::operator() ()
#6	0x00007ff8262a3a32 in swift::Demangle::__runtime::Demangler::demangleSymbolicReference ()
#7	0x00007ff82629fd78 in swift::Demangle::__runtime::Demangler::demangleType ()
#8	0x00007ff8262777c7 in swift_getTypeByMangledNameImpl ()
#9	0x00007ff826272a8b in swift_getTypeByMangledName ()
#10	0x00007ff826272d87 in swift_getTypeByMangledNameInContextImpl ()
#11	0x000000010a7f34ab in __swift_instantiateConcreteTypeFromMangledName ()
#12	0x000000010a7f393c in _runTaskForBridgedAsyncMethod(_:) ()

I did a bit more digging in the disassembly and found that the error message passed to swift::fatalError() is

"Failed to look up symbolic reference at %p - offset %d - symbol %s in %s\n"

The final parameter is the executable path. The second last parameter is the following string: "symbolic _____Sg ScP"

I get the feeling that the swift compiler attempts to bind to a symbol which the objc compiler didn't export. On the objC side the call simply looks the following:

[object method:^() {}];

Correction: the crash does not happen when the completion handler is called, but very early before the actual code of the Swift async function is executed.

Meanwhile I have done tests with a minimal working example and it worked, there was no crash. So now I need to figure out why I get the crash in the large project.

I found out that the Swift compiler generates different type reference symbols depending on the -target compiler option and somehow this seems to play a role in our project. Will keep investigating.

I have been able to fix the crash reported above by adding /usr/lib/swift to 'Runpath Search Paths'. I noticed that Xcode implicitly sets this path in my other small test project even though the corresponding entry in the build settings is empty. In my main project this did not happen.

It would be nice if the app would print something meaningful instead of cryptically crashing. Normally if there is an issue with dynamic libraries a message is printed that the library could not be loaded. Here nothing of that sort happened.

AppTransaction: how to use in ObjC apps (now that we are forced to use it after the exit(173) deprecation)
 
 
Q