@@ -16,57 +16,25 @@ import Foundation
1616import StoreKit
1717import FirebaseAnalytics
1818
19- // Pure Swift worker to prevent compiler generation bugs with witness tables
20- // when mixing @objc and Swift Concurrency on older iOS deployment targets.
21- @available ( iOS 15 . 0 , tvOS 15 . 0 , macOS 12 . 0 , watchOS 8 . 0 , * )
22- internal struct AppleTransactionWorker {
23- static func findAndLogTransaction( id: String ) async -> Bool {
24- // Using a standard while loop instead of `for await` avoids a known
25- // Xcode 15+ compiler bug where corrupted witness tables are generated
26- // for AsyncSequences when targeting iOS 15/16.
27- var iterator = Transaction . all. makeAsyncIterator ( )
28- while let verificationResult = await iterator. next ( ) {
29- if case . verified( let transaction) = verificationResult {
30- if String ( transaction. id) == id {
31- Analytics . logTransaction ( transaction)
32- return true
33- }
34- }
35- }
36- return false
37- }
38- }
39-
4019@objc public class AppleTransactionVerifier : NSObject {
4120
42- // Notice: To avoid C-pointer ABI layout issues across Swift boundaries,
43- // this interface relies purely on a standard Swift closure. The calling
44- // C++ layer captures any necessary pointers within an Objective-C block.
4521 @objc public static func verify( transactionId: String , completion: @escaping @Sendable ( Bool ) -> Void ) {
4622 if #available( iOS 15 . 0 , tvOS 15 . 0 , macOS 12 . 0 , watchOS 8 . 0 , * ) {
47- // Create a pure Swift String copy of the transaction ID.
48- // This ensures we do not rely on the memory of the bridged Objective-C
49- // NSString, which might be destroyed by the calling C++ thread before
50- // the asynchronous Task completes.
51- let safeId = String ( transactionId)
52-
53- // Use a detached task to avoid inheriting any C++/ObjC thread context.
54- // This prevents EXC_BAD_ACCESS during `swift_task_alloc` on iOS 15/16.
55- Task . detached ( priority: . userInitiated) { [ safeId, completion] in
56- let isFound = await AppleTransactionWorker . findAndLogTransaction ( id: safeId)
57-
58- // Return to the main thread to ensure thread safety when the C++
59- // layer receives the callback and manages internal state.
60- DispatchQueue . main. async {
61- completion ( isFound)
23+ Task {
24+ var iterator = Transaction . all. makeAsyncIterator ( )
25+ while let verificationResult = await iterator. next ( ) {
26+ if case . verified( let transaction) = verificationResult {
27+ if String ( transaction. id) == transactionId {
28+ Analytics . logTransaction ( transaction)
29+ completion ( true )
30+ return
31+ }
32+ }
6233 }
63- }
64- } else {
65- // Not on a supported OS, resolve immediately on the main thread
66- // to avoid potential deadlocks if called while holding a C++ mutex.
67- DispatchQueue . main. async {
6834 completion ( false )
6935 }
36+ } else {
37+ completion ( false )
7038 }
7139 }
7240}
0 commit comments