Skip to content

Conversation

@ajpallares
Copy link
Member

@ajpallares ajpallares commented Aug 7, 2025

Motivation

As stated in SK1's SKPaymentQueue's storefront property documentation:

storefront is a synchronous API that may take significant time to return. Don’t use storefront in a time-sensitive thread, such as during app launch. To get asynchronous behavior, dispatch it to a separate queue, or use the asynchronous current property of Storefront instead.

The iOS SDK is currently using this API as a synchronous API and not considering that it may be a blocking call.

In addition, this API has been deprecated in iOS 26.

Description

The main changes in this PR is to make SystemInfo's storefront an async getter. Under the hood:

  • It uses SK2's Storefront's current async property by default in iOS 15+.
  • In iOS 13 and iOS 14, it still uses SK1's SKPaymentQueue's storefront, but converted into an async operation with Task.detached, to avoid blocking (as the documentation recommends).

Most of the changes are a consequence of making it async: e.g. HTTPClient, diagnostics tracking methods that receive the storefront and unit tests.

@emerge-tools
Copy link

emerge-tools bot commented Aug 7, 2025

📸 Snapshot Test

1 modified, 692 unchanged

Name Added Removed Modified Renamed Unchanged Errored Approval
RevenueCat
com.revenuecat.PaywallsTester.mac-catalyst-optimized-for-mac
0 0 1 0 230 0 ⏳ Needs approval
RevenueCat
com.revenuecat.PaywallsTester.mac-catalyst-scaled-to-match-ipad
0 0 0 0 231 0 N/A
RevenueCat
com.revenuecat.PaywallsTester
0 0 0 0 231 0 N/A

🛸 Powered by Emerge Tools

@ajpallares ajpallares marked this pull request as ready for review August 7, 2025 12:02
@ajpallares ajpallares requested a review from a team August 7, 2025 12:02
Copy link
Contributor

@tonidero tonidero left a comment

Choose a reason for hiding this comment

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

Just some comments and questions... But I don't think anything is blocking. Thanks for doing this!

set { self.systemInfo.finishTransactions = newValue.finishTransactions }
}

@objc public var storeFrontCountryCode: String? {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we deprecate this property and provide an async/callback API?

Copy link
Member Author

Choose a reason for hiding this comment

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

I thought about it, but wasn't really sure.

It can be done in a separate PR, with these considerations:

  • There's one downside: the new API wouldn't be ObjC-compatible. We could create a ObjC-compatible method with a completion handler for it if we think it's needed.
  • I can't think of a good name for it, as the most straightforward one is taken. We could "fix" the existing name by removing the capital case "F", having the new property be storefrontCountryCode, but I think it's too similar to the one we'd be deprecating.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah that's a good point... I think this is something we might want to consider breaking in the next major. Should probably take note in Linear so we don't forget for the next major, whenever that is.

…ient

A way deeper rewrite is needed to introduce `Task`s in `HTTPClient` without breaking the current behavior as defined by unit tests
@ajpallares ajpallares force-pushed the update-storefront-apis branch 3 times, most recently from 4e4ac34 to b85bc3f Compare August 7, 2025 16:43
@ajpallares ajpallares force-pushed the update-storefront-apis branch from b85bc3f to 449f4f0 Compare August 7, 2025 16:45
Comment on lines 163 to 179
/// Returns the current storefront synchronously.
///
/// Because this method is synchronous, it should be used only from a background thread to avoid blocking
/// the main thread.
private func getStorefrontSynchronously() -> StorefrontType? {
let storefront: Atomic<StorefrontType?> = .init(nil)
let semaphore = DispatchSemaphore(value: 0)

Task {
storefront.value = await self.systemInfo.storefront
semaphore.signal()
}

semaphore.wait()

return storefront.value
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Trying to introduce an async call in HTTPClient got more complicated than I expected. Introducing a Task to asynchronous get the Storefront for the defaultHeaders in this call

self.perform(request: .init(httpRequest: request,
authHeaders: self.authHeaders,
defaultHeaders: self.defaultHeaders,
verificationMode: verificationMode ?? self.systemInfo.responseVerificationMode,
internalSettings: self.systemInfo.dangerousSettings.internalSettings,
completionHandler: completionHandler))

cannot be done without breaking the expectation of requests happening serially, as tested by

func testPerformSerialRequestWaitsUntilRequestsAreDoneBeforeStartingNext() {

A deeper migration of HTTPClient to support async/await tasks would be needed, I think.

For now, I just went with this workaround to keep the storefront getter synchronous by using a Semaphore. Note that while this would block the current thread, this is OK as long as this blocking never happens in the main thread, which is true since all network operations are added to a background queue. See

let config = BackendConfiguration(httpClient: httpClient,
operationDispatcher: operationDispatcher,
operationQueue: QueueProvider.createBackendQueue(),
diagnosticsQueue: QueueProvider.createDiagnosticsQueue(),
systemInfo: systemInfo,
offlineCustomerInfoCreator: offlineCustomerInfoCreator,
dateProvider: dateProvider)

The (only) advantage here is that we are now using SK2's storefront when available.

The alternative would be keep using the existing (but deprecated since iOS 26) SK1's storefront getter which is synchronous but also blocking as per its documentation. The advantage is that we wouldn't need this workaround.

@RevenueCat/coresdk please, let me know what you think!

@ajpallares
Copy link
Member Author

@RCGitBot please test

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants