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!

Answered by DTS Engineer in 810009022
All I need to do is execute one line of Swift:

Now that’s something I can help with. Here how I did this:

  1. I created a new project from an Objective-C template.

  2. I choose File > New > File from Template and then chose the macOS > Cocoa Class template, with Swift as the language.

  3. This asked whether I want to create a bridging header. I allowed that, but it’s not relevant to this discussion.

  4. I edited the file to look like this:

    import StoreKit
    
    @objc
    class MyAppTransaction: NSObject {
    
        @objc
        class func checkReceipt() async -> String {
            do {
                let verificationResult = try await AppTransaction.shared
                switch verificationResult {
                case .unverified(_, _):
                    return "NG"
                case .verified(_):
                    return "OK"
                }
            } catch {
                return "ERR"
            }
        }
    }
    

    I’ve chosen silly status values. You can tweak these values, or even the types, as you wish. However, the types must be Objective-C representable. In this case I’m using String, which bridges to NSString.

  5. On the Objective-C side, I added this:

    #import "Test764537-Swift.h"
    

    and then called the Swift code like this:

    [MyAppTransaction checkReceiptWithCompletionHandler:^(NSString * _Nonnull status) {
        NSLog(@"status: %@", status);
    }];
    

I’m working with Xcode 16.0, but I don’t think there’s anything Xcode 16 specific about this.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

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.

I have the same problems. The app is reporting that exit(173) is no longer available, even though it isn't called. The alert tells me to use Transaction or AppTransaction. These are only available in Swift and they are for in-app purchases. The documentation tells me to use AppTransaction or Transaction. These are also Swift but not objective C APIs. I have created a header as described in the documentation for Importing Swift into Objective-C but this is not helpful because I need to read the Objective C declarations to call the Swift APIs from my Objective C code. I can't because the productname-Swift.h file is empty. It does seem to understand Objective-C versions of some APIs, such as NSPersistentHistoryTransaction *myTransaction but making wild guesses about the APIs in Objective-C isn't the real way to do this. All I need t do is call the method to validate the receipt and also if there is some way to get a test receipt to debug my code as I did with exit(173) it would be a help.

After carefully studying the documentation for Importing Swift into Objective-C, I am unable to make it work.

First of all the document says that the header is generated from my Product Module name. Searching the Developer documentation and the XCode Help for Product Module Name yields no results. Looking at my project's Target Build Settings I see in Packaging a field called Product Module Name. Since this is the closest thing I can find to the description in the document I put the name of my app in the fields.

Then I created a header file with the name appName-Swift.h in my project. I built the project but the header is empty and #importing it into a .m file doesn't provide my code with the Swift types.

Has anyone actually been able to do this as described in the document? For me, carefully studying the developer documentation often brings me to a frustrating dead end like this.

I have made some progress. I added the forward declaration:

@class AppTransaction;
@interface AppTransaction : NSObject
- (AppTransaction *)returnAppTransaction;
@end

to myappname-Swift.h header.

Now XCode recognizes AppTransaction in my code. BUT AppTransaction is declared as struct and XCode doesn't recognize .shared or .VerificationResult. members.

If I remove the declaration, XCode won't recognize AppTransaction - undeclaredidentifier.

I can write a Swift function to verify the receipt:

func chekReceipt() async{
    do{
        let verificationResult =  try await AppTransaction.shared
        switch verificationResult {
        case .verified(let appTransaction):
            var string = "receipt verified"
            print(string)
        case .unverified(let appTransaction, let verificationError):
            var string = "receipt unverified"
            print(string)
        }
    }
    catch{
        var string = "checkReceipt threw an error"
        print(string)
        }
    }

But I can't call it from Objective C.

There are other issues: I am targeting my project for macOS 10.11 (the earliest version you can target with XCode) to provide backwards compatibility. Lots of my users are on older Macs and don't use 10.15. AppTransaction is only available on 10.13+ so I will have to support the older verification code for a long time and if Apple breaks this it will be a hassle for me and my users.

Also the sample code for AppTransaction won't build. You have to add the keyword "async". This is not in my book Swift Programming 3rd Edition or the sample code so I had to go through a bunch of ** to figure this out. Criticism of Swift - it's a moving target.

But I can't call it from Objective C.

What happens when you try, using the ...withCompletionHandler syntax?

(I have a very vague recollection of having to make something that could have been a function a static class method instead, possibly related to this.)

Just to be perfectly clear:

The developer documentation says I can get XCode to generate a header that will allow a .m file to recognize Swift types and key words.

I carefully followed the instructions - I got it to create the header. I included the header in my project. I imported the header into my .m file. I am including the Store Kit framework in my build settings. I imported the header storekit/storekit.h in my file.

It doesn't matter how I try to use AppTransaction because XCode doesn't recognize Swift ketwords of types.

Also I included the StoreKit.framework and SwiftData.framework in my target build settings. Is there some (undocumented?) trick to get XCode to recognize the Store Kit types?

It doesn't matter how I try to use AppTransaction because XCode doesn't recognize Swift ketwords of types.

Since no-one else is replying, I’m going to try and help - but beware, I don’t think I know much more than you. So:

In an earlier post you showed your Swift checkReceipt function. But I don’t really understand what fails to work now. You just said ”I can’t call it from objC”. Can you show your objC code that tries to call that Swift function, and the error message that you get when you try to compile it?

Is there some (undocumented?) trick to get XCode to recognize the Store Kit types?

I wouldn’t try to call StoreKit’s Swift functions directly from objC. Write a short Swift function that calls StoreKit, and call that from objC - exactly as you showed above, but return a bool that the objC tests.

All I need to do is execute one line of Swift:

let verificationResult = try await AppTransaction.shared

and look at verification result.

According to the less-than-worthless documentation here:

https://developer.apple.com/documentation/swift/importing-swift-into-objective-c

“You can work with types declared in Swift from within the Objective-C code in your project by importing an Xcode-generated header file.” Generating the header file and including it in my project and .m file doesn't make XCode recognize the types.

Theoretically you could call some C version of the above line from your .m file. Carefully following the incomplete documentation I can’t do it so Xcode will recognize the three types, verificationResult (also spelled as VerificationResult, upper case ‘V’ in the documentation), AppTransaction and its method, shared. Including AppTransaction (and verificationResult and shared) in my .m code results in a error:

Use of undeclared identifier ‘AppTransaction’ (all three types); XCode doesn't complain about the syntax, it just doesn't recognize the types.

Your advice not to try to call StoreKit’s Swift functions directly from objC is good, since it’s not really possible using the developer documentation.

The documentation here:

https://developer.apple.com/documentation/swift/migrating-your-objective-c-code-to-swift/

Tells me how to sub-class an Objective-C class in Swift to use it in an Objective-C project, but not to do what you describe “Write a short Swift function that calls StoreKit, and call that from objC”.

Maybe you know about some documentation describing how to do this?

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