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"

I thought that XCode was supposed to create this?

Yes. If I were you I'd look at the timestamp on that file and see if Xcode has just created an empty file, or whether it has done nothing at all and that file is left over from earlier. And I'd look in the build logs.

As you can tell, I'm out of my depth here...

I recommend that you try to reproduce this in a tiny test project. Create a new project from one of the built-in templates, add that Swift file to it, and then see if the TTT-Swift.h file renders as expected.

If it doesn’t, post a link to your project here and I’ll take a look.

If it does, you have a working example and you can compare it to your non-working main project.

Share and Enjoy

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

I already have a test project. I also created a test project App->objective-C. With both of these when I add the receipt validation Swift file to the projects XCode asks me if I want to create a bridging header. It creates the name-Bridging-header.h.

This is not the bridging head I want. I want the name-Swift.h header. The name-bridging-headers are empty.

Shouldn't they at least have this:

#ifndef name_Bridging_Header_h

#define name_Bridging_Header_h

#endif /* name_Bridging_Header_h */

So XCode doesn't create the bridging header. Is there something missing in the build settings or something?

Update: things have gone from bad to worse. I removed the Swift files from both projects, build them and when I add the Swift file it adds it to the projects but doesn't ask to create a bridging header.

Update 2: If I delete the Swift files, clean and rebuild my test project and add the Swift File, XCode will automatically create the two bridging headers. They will have the #ifndef, #define #endifs in them but no Objective-C prototype in the name-Swift.h header for the Swift Function to verify receipts.

I removed the Swift files from both projects, build them and when I add the Swift file it adds it to the projects but doesn't ask to create a bridging header

Right. That’s expected. Xcode only suggests adding a bridging header the first time you add code in the other language.

As to why things aren’t working for you, it’s hard to say. I described my process in some detail in my earlier post and it worked just fine for me.

If you upload a test project somewhere and post the URL here, I’d be happy to take a look. It’d be best you started with a new project, following the steps in my my earlier post. That’ll avoid any confusion created by your valiant attempts to get this working (-:

IMPORTANT If you have problems posting the URL, see tip 14 in Quinn’s Top Ten DevForums Tips.

Share and Enjoy

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

I got this to work and this is REALLY WEIRD:

If I #import the name-Swift.h header in a .m file the project will build and I can use the MyAppTransaction object in my .m files.

BUT XCode doesn't actually create the file in my project directories. Apparently it's cryptic, invisible, implied or something. It would have saved me a huge amount of trouble if the developer documentation described this or if someone mentioned it.

Also I never would have guessed that you would have to invoke the method using that syntax. Are a lot of the calls to Swift objects as weird as that? Is there some documentation about this?

Update: I spoke too soon. This will work in my test project but not on a major project for a Mac App on the App Store. Something in the build settings?

It would be helpful if I could read the name-Swift.h header to see how to invoke the Swift object methods. The project runs with this invisible header included but it isn't visible in my project hierarchy, Find can't find it, Spotlight can't find it, Open Quickly can't find it and XCode -> Find can't find it. This is extremely unhelpful.

% find ~/Library/Developer/Xcode -name '*-Swift.h' -print

Also I never would have guessed that you would have to invoke the method using that syntax.

What syntax, exactly?

BUT XCode doesn't actually create the file in my project directories.

Correct. That header is a build product and thus gets placed in the build products directory.

If you want to see its contents, command click on it. For example, in my test project I have this line:

#import "Test764537-Swift.h"

If I command click on the text inside the quotes, it takes me to the generated header:

… lots of stuff elided …

SWIFT_CLASS("_TtC10Test76453716MyAppTransaction")
@interface MyAppTransaction : NSObject
+ (void)checkReceiptWithCompletionHandler:(void (^ _Nonnull)(NSString * _Nonnull))completionHandler;

… lots more stuff elided …

I can then chose File > Show in Finder to find out where it’s really located. In my case that happened to be /Users/quinn/Library/Developer/Xcode/DerivedData/Test764537-foivrohmevxnvtewlpwruoiwbkdd/Index.noindex/Build/Intermediates.noindex/Test764537.build/Debug/Test764537.build/DerivedSources/Test764537-Swift.h.

Hence my comment about it being a build product.

Open Quickly can't find it

I wouldn’t expect any of the others to work — again, this is a build product — but I would’ve expected File > Open Quickly to work, because it usually mirrors the behaviour of command click. IMO you should file a bug about that. Please post your bug number, just for the record.

What syntax, exactly?

Yeah, likewise. I’m not sure what syntax you’re having problems with.

Share and Enjoy

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

endecotp: the syntax that surprised be was:

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

I'm not having a problem with it but I never saw it until it was posted here. Also I don't know how Swift types map to Objective-C types. Quinn mentioned that String is equivalent to NSString. This makes sense but it would be useful if all of this was documented somewhere.

I never saw it until it was posted here.

It's a normal objC class-method call, passing a block. Maybe it's the block that is new to you?

Anyway, is this working for you now?

but I never saw it until it was posted here.

It’s quite standard. You can see similar examples all over our SDK. For example, the +[PHPhotoLibrary requestAuthorizationForAccessLevel:handler:] method uses this syntax.

it would be useful if all of this was documented somewhere.

You’ll find a bunch of useful interoperability info under the Language Interoperability with Objective-C and C group in the Swift docs. For example, Working with Foundation Types has a table for these bridged types.

Share and Enjoy

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

I was working on this today and I encountered more strange problems:

Even though the chekReceipt() Swift function returns a String (-> String), the MyAppTransaction class method in the automatically generated name-Swift.h file is defined as (void). This won’t work because if the verificationResult is either .unverified or there is an error I need to post an alert. You can only post an alert from the main thread. Calling one from the completion handler will cause a crash. I have to return a result to the main thread to create an alert. I tried to edit the name-Swift.h header to change the return type of the checkReceiptWithCompletionHandler method to string but I can’t, it’s apparently read only.

Within the scope of the completion handler you can use the status string, for example NSLog(@"status: %@", status);, but you can’t return it to the calling function. I was hoping to copy it to the calling function with something like this:

NSString* verifyWithStoreKit(void) { NSString* myString;

[MyAppTransaction checkReceiptWithCompletionHandler: ^(NSString * _Nonnull status)
 {
    myString = [[NSString string] initWithString: status];
}];
//@autoreleasepool {
    // Setup code that might create autoreleased objects goes here.
//}
return myString;

}

But Xcode won’t build instructions like this or myString is nil. What can I do?

Even though the chekReceipt() Swift function returns a String (-> String), the MyAppTransaction class method in the automatically generated name-Swift.h file is defined as (void).

The swift function returns a string asynchronously. Since objC doesn't have async functions, it returns void and passes the string later to the completion handler.

I tried to edit the name-Swift.h header to change the return type

That's not going to work.

Within the scope of the completion handler you can use the status string ... but you can’t return it to the calling function.

Correct, and there is no way to avoid that. You need to restructure to work with this.

I need to post an alert. You can only post an alert from the main thread. Calling one from the completion handler will cause a crash.

In that case, in the completion handler you can dispatch to the main thread to post the alert. Pseudo-code:

    dispatch_async(dispatch_get_main_queue(), ^{
      present_alert(message);
    });

Thank you so much. Theoretically I could return status from the block but this won't work in this case. I could also do something like post a notification. Does the Swift function have to be async?

Does the Swift function have to be async?

Yes, because AppTransaction.shared is async.

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