From 5696e6b0c6855e009487af61a2bf2d61df167d09 Mon Sep 17 00:00:00 2001 From: Mark Villacampa Date: Tue, 28 Jan 2025 00:33:38 +0100 Subject: [PATCH 1/7] Fix crash on iOS 11 and 12 when compiling with Xcode 16 and instantiating an array with type @MainActor lambda var foo: [@MainActor @Sendable () -> Void] = [] --- Sources/Purchasing/StoreKit1/StoreKitRequestFetcher.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/Purchasing/StoreKit1/StoreKitRequestFetcher.swift b/Sources/Purchasing/StoreKit1/StoreKitRequestFetcher.swift index 8f98ca9332..d7f664b2ef 100644 --- a/Sources/Purchasing/StoreKit1/StoreKitRequestFetcher.swift +++ b/Sources/Purchasing/StoreKit1/StoreKitRequestFetcher.swift @@ -27,7 +27,7 @@ class StoreKitRequestFetcher: NSObject { private let requestFactory: ReceiptRefreshRequestFactory private var receiptRefreshRequest: SKRequest? - private var receiptRefreshCompletionHandlers: [@MainActor @Sendable () -> Void] + private var receiptRefreshCompletionHandlers: [@Sendable () -> Void] private let operationDispatcher: OperationDispatcher init(requestFactory: ReceiptRefreshRequestFactory = ReceiptRefreshRequestFactory(), @@ -38,7 +38,7 @@ class StoreKitRequestFetcher: NSObject { self.receiptRefreshCompletionHandlers = [] } - func fetchReceiptData(_ completion: @MainActor @Sendable @escaping () -> Void) { + func fetchReceiptData(_ completion: @Sendable @escaping () -> Void) { self.operationDispatcher.dispatchOnWorkerThread { self.receiptRefreshCompletionHandlers.append(completion) @@ -94,7 +94,7 @@ private extension StoreKitRequestFetcher { self.receiptRefreshCompletionHandlers = [] for handler in completionHandlers { - self.operationDispatcher.dispatchOnMainActor { + self.operationDispatcher.dispatchOnMainThread { handler() } } From 7cdb21fbd09e0b115a1801eadc6b579f0809aaab Mon Sep 17 00:00:00 2001 From: Mark Villacampa Date: Tue, 28 Jan 2025 15:17:49 +0100 Subject: [PATCH 2/7] fix --- Tests/UnitTests/Mocks/MockRequestFetcher.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/UnitTests/Mocks/MockRequestFetcher.swift b/Tests/UnitTests/Mocks/MockRequestFetcher.swift index 96fe52e3b8..10efab34f6 100644 --- a/Tests/UnitTests/Mocks/MockRequestFetcher.swift +++ b/Tests/UnitTests/Mocks/MockRequestFetcher.swift @@ -9,7 +9,7 @@ class MockRequestFetcher: StoreKitRequestFetcher { var refreshReceiptCalledCount = 0 var refreshReceiptCalled = false - override func fetchReceiptData(_ completion: @MainActor @Sendable @escaping () -> Void) { + override func fetchReceiptData(_ completion: @Sendable @escaping () -> Void) { self.refreshReceiptCalledCount += 1 self.refreshReceiptCalled = true From 64b17b9e68651c95586c4e320138b710be9260fd Mon Sep 17 00:00:00 2001 From: Andy Boedo Date: Mon, 3 Feb 2025 14:36:00 -0300 Subject: [PATCH 3/7] removed one problematic MainActor reference --- Sources/Purchasing/Purchases/Purchases.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/Purchasing/Purchases/Purchases.swift b/Sources/Purchasing/Purchases/Purchases.swift index a04fd3e022..c5ea0ba0ab 100644 --- a/Sources/Purchasing/Purchases/Purchases.swift +++ b/Sources/Purchasing/Purchases/Purchases.swift @@ -33,10 +33,10 @@ public typealias PurchaseResultData = (transaction: StoreTransaction?, /** Completion block for ``Purchases/purchase(product:completion:)`` */ -public typealias PurchaseCompletedBlock = @MainActor @Sendable (StoreTransaction?, - CustomerInfo?, - PublicError?, - Bool) -> Void +public typealias PurchaseCompletedBlock = @Sendable (StoreTransaction?, + CustomerInfo?, + PublicError?, + Bool) -> Void /** Block for starting purchases in ``PurchasesDelegate/purchases(_:readyForPromotedProduct:purchase:)`` From 74722be921f49638d2863efaea2170bd044dd770 Mon Sep 17 00:00:00 2001 From: Mark Villacampa Date: Thu, 6 Feb 2025 17:27:21 +0100 Subject: [PATCH 4/7] Make the internal block type non-@MainActor, and make it wrap the external @MainActor block. This way we avoid hitting the iOS 11 crash when initiailising a collection with a @MainActor block type, and we dont change the public interface. --- Sources/Purchasing/Purchases/Purchases.swift | 2 +- .../Purchases/PurchasesOrchestrator.swift | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Sources/Purchasing/Purchases/Purchases.swift b/Sources/Purchasing/Purchases/Purchases.swift index c5ea0ba0ab..70e816d83c 100644 --- a/Sources/Purchasing/Purchases/Purchases.swift +++ b/Sources/Purchasing/Purchases/Purchases.swift @@ -33,7 +33,7 @@ public typealias PurchaseResultData = (transaction: StoreTransaction?, /** Completion block for ``Purchases/purchase(product:completion:)`` */ -public typealias PurchaseCompletedBlock = @Sendable (StoreTransaction?, +public typealias PurchaseCompletedBlock = @MainActor @Sendable (StoreTransaction?, CustomerInfo?, PublicError?, Bool) -> Void diff --git a/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift b/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift index d7f7e4e433..30c22379db 100644 --- a/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift +++ b/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift @@ -44,7 +44,10 @@ final class PurchasesOrchestrator { private let _allowSharingAppStoreAccount: Atomic = nil private let presentedOfferingContextsByProductID: Atomic<[String: PresentedOfferingContext]> = .init([:]) private let presentedPaywall: Atomic = nil - private let purchaseCompleteCallbacksByProductID: Atomic<[String: PurchaseCompletedBlock]> = .init([:]) + private let purchaseCompleteCallbacksByProductID: Atomic<[String: @Sendable (StoreTransaction?, + CustomerInfo?, + PublicError?, + Bool) -> Void]> = .init([:]) private var appUserID: String { self.currentUserProvider.currentAppUserID } private var unsyncedAttributes: SubscriberAttribute.Dictionary { @@ -969,7 +972,11 @@ private extension PurchasesOrchestrator { return false } - callbacks[productIdentifier] = completion + callbacks[productIdentifier] = { transaction, customerInfo, error, cancelled in + self.operationDispatcher.dispatchOnMainActor { + completion(transaction, customerInfo, error, cancelled) + } + } return true } } @@ -978,7 +985,10 @@ private extension PurchasesOrchestrator { forTransaction transaction: StoreTransaction ) -> PurchaseCompletedBlock? { return self.purchaseCompleteCallbacksByProductID.modify { - return $0.removeValue(forKey: transaction.productIdentifier) + let block = $0.removeValue(forKey: transaction.productIdentifier) + return { transaction, customerInfo, error, cancelled in + block?(transaction, customerInfo, error, cancelled) + } } } From bd8a70bc361a0cde87bb84e84773c002f0b88276 Mon Sep 17 00:00:00 2001 From: Mark Villacampa Date: Thu, 6 Feb 2025 17:42:58 +0100 Subject: [PATCH 5/7] fix leak --- Sources/Purchasing/Purchases/PurchasesOrchestrator.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift b/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift index 30c22379db..a6accab613 100644 --- a/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift +++ b/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift @@ -972,8 +972,8 @@ private extension PurchasesOrchestrator { return false } - callbacks[productIdentifier] = { transaction, customerInfo, error, cancelled in - self.operationDispatcher.dispatchOnMainActor { + callbacks[productIdentifier] = { [weak self] transaction, customerInfo, error, cancelled in + self?.operationDispatcher.dispatchOnMainActor { completion(transaction, customerInfo, error, cancelled) } } From b0d97880f88c4ec972334cd88549af01087bd77d Mon Sep 17 00:00:00 2001 From: Andy Boedo Date: Fri, 7 Feb 2025 13:06:32 -0300 Subject: [PATCH 6/7] removed reference to @MainActor when getting the completion block --- Sources/Purchasing/Purchases/PurchasesOrchestrator.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift b/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift index a6accab613..3655994a7e 100644 --- a/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift +++ b/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift @@ -983,7 +983,10 @@ private extension PurchasesOrchestrator { func getAndRemovePurchaseCompletedCallback( forTransaction transaction: StoreTransaction - ) -> PurchaseCompletedBlock? { + ) -> (@Sendable (StoreTransaction?, + CustomerInfo?, + PublicError?, + Bool) -> Void)? { return self.purchaseCompleteCallbacksByProductID.modify { let block = $0.removeValue(forKey: transaction.productIdentifier) return { transaction, customerInfo, error, cancelled in From a5f2aed8d360f1e4e1da1bb0299c724ff1905f88 Mon Sep 17 00:00:00 2001 From: Mark Villacampa Date: Fri, 7 Feb 2025 18:20:58 +0100 Subject: [PATCH 7/7] remove @MainActor from the optional return type as it crashes when accessing the type metadata because the method is generic --- .../Purchasing/Purchases/PurchasesOrchestrator.swift | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift b/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift index 3655994a7e..822b7e4043 100644 --- a/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift +++ b/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift @@ -984,14 +984,11 @@ private extension PurchasesOrchestrator { func getAndRemovePurchaseCompletedCallback( forTransaction transaction: StoreTransaction ) -> (@Sendable (StoreTransaction?, - CustomerInfo?, - PublicError?, - Bool) -> Void)? { + CustomerInfo?, + PublicError?, + Bool) -> Void)? { return self.purchaseCompleteCallbacksByProductID.modify { - let block = $0.removeValue(forKey: transaction.productIdentifier) - return { transaction, customerInfo, error, cancelled in - block?(transaction, customerInfo, error, cancelled) - } + return $0.removeValue(forKey: transaction.productIdentifier) } }