Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v4: Fix crash in iOS 11-12 when using MainActor #4718

Open
wants to merge 7 commits into
base: release/4.43.3
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Sources/Purchasing/Purchases/Purchases.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 = @MainActor @Sendable (StoreTransaction?,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one keeps the @MainActor. I'm guessing it's not used anymore in this v4. If so, perhaps we should remove it completely to prevent potential future errors if we need to make a new release of v4?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the concern around that one was about making further changes to the API - like, if your code relies on this being @MainActor, you might have broken compilation after upgrading.

I mean, the original problem was to add @MainActor in a non-minor anyway, but kinda don't want to repeat the same issue twice

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might as well kill the extra space so it doesn't even show up in the diff tho 😅

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think it makes sense 👍

CustomerInfo?,
PublicError?,
Bool) -> Void

/**
Block for starting purchases in ``PurchasesDelegate/purchases(_:readyForPromotedProduct:purchase:)``
Expand Down
16 changes: 13 additions & 3 deletions Sources/Purchasing/Purchases/PurchasesOrchestrator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ final class PurchasesOrchestrator {
private let _allowSharingAppStoreAccount: Atomic<Bool?> = nil
private let presentedOfferingContextsByProductID: Atomic<[String: PresentedOfferingContext]> = .init([:])
private let presentedPaywall: Atomic<PaywallEvent?> = 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 {
Expand Down Expand Up @@ -969,14 +972,21 @@ private extension PurchasesOrchestrator {
return false
}

callbacks[productIdentifier] = completion
callbacks[productIdentifier] = { [weak self] transaction, customerInfo, error, cancelled in
self?.operationDispatcher.dispatchOnMainActor {
completion(transaction, customerInfo, error, cancelled)
}
}
return true
}
}

func getAndRemovePurchaseCompletedCallback(
forTransaction transaction: StoreTransaction
) -> PurchaseCompletedBlock? {
) -> (@Sendable (StoreTransaction?,
CustomerInfo?,
PublicError?,
Bool) -> Void)? {
return self.purchaseCompleteCallbacksByProductID.modify {
return $0.removeValue(forKey: transaction.productIdentifier)
}
Expand Down
6 changes: 3 additions & 3 deletions Sources/Purchasing/StoreKit1/StoreKitRequestFetcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to understand, we shouldn't be using @MainActor, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exclusively in v4 of the iOS SDK. This is because it has a deployment target of iOS 11

Copy link
Member

@aboedo aboedo Jan 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

v5 is iOS 13+ so we can use it there

private let operationDispatcher: OperationDispatcher

init(requestFactory: ReceiptRefreshRequestFactory = ReceiptRefreshRequestFactory(),
Expand All @@ -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)

Expand Down Expand Up @@ -94,7 +94,7 @@ private extension StoreKitRequestFetcher {
self.receiptRefreshCompletionHandlers = []

for handler in completionHandlers {
self.operationDispatcher.dispatchOnMainActor {
self.operationDispatcher.dispatchOnMainThread {
handler()
}
}
Expand Down
2 changes: 1 addition & 1 deletion Tests/UnitTests/Mocks/MockRequestFetcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down