Migrating from Objective-C to Swift required a number of API changes, but we feel that the changes resulted in the SDK having a more natural feel for developers.
purchases-ios
v4 requires using Xcode 13.2 or newer.
It also updates the minimum deployment targets for iOS, macOS and tvOS.
v3 | v4 | |
---|---|---|
iOS | 9.0 | 11.0 |
tvOS | 9.0 | 11.0 |
macOS | 10.12 | 10.13 |
watchOS | 6.2 | 6.2 (unchanged) |
To start us off, our framework name changed from Purchases
to RevenueCat
! 😻 You'll now need to explicitly import RevenueCat
instead of Purchases
.
Before | After |
---|---|
import Purchases |
import RevenueCat |
Before | After |
---|---|
@import Purchases; |
@import RevenueCat; |
Select your target in Xcode, then go to Build Phases, and ensure that your target's Link Binary with Libraries
section
references RevenueCat
, and remove the reference to Purchases
if it was still there.
Before | After |
---|---|
Note:
Due to an Xcode bug, you might run into a Workspace Integrity error after upgrading, with a message that looks like
"Couldn't load project PurchaseTester"
.
If this happens, you can fix it with the following steps:
- In Xcode, go to Product -> Clean Build Folder
- Quit and re-open Xcode
In your Podfile, update the reference to the Pod from Purchases
to RevenueCat
.
Before | After |
---|---|
pod 'Purchases' |
pod 'RevenueCat' |
Select your target in Xcode, then go to Build Phases, and ensure that your target's Link Binary with Libraries
section
references RevenueCat
, and remove the reference to Purchases
if it was still there.
Do the same with the Embed Frameworks section.
Before | After |
---|---|
We highly recommend moving into XCFrameworks, since these have a simpler setup and prevent compatibility issues with multi-platform setups.
Carthage has a migration guide to move into XCFrameworks available here.
After migrating into XCFrameworks, follow the steps outlined in 1.3.1 to set up the RevenueCat.xcframework
.
If you can't move into XCFrameworks, you will still need to update the Link Binary with Libraries
phase as outlined
in 1.3.1 (only using a .framework
instead of .xcframework
).
After that, update the your input.xcfilelist
and output.xcfilelist
for the Run Script phase of Carthage frameworks,
replacing Purchases.framework
with RevenueCat.framework
.
When building your project using v4, Xcode should automatically provide one-click fixes methods and types that have been renamed. For the most part, the migration should be doable by just building and applying Xcode's automatic fix-its when they pop up.
If you see any issues or new APIs that fix-its didn't cover, we'd appreciate bug reports!
You might run into compilation errors with a message like Error:
'_' is not a member type of class 'RevenueCat.Purchases'`.
The reason is that the class Purchases
is no longer the parent of classes such as Offerings
.
You should reference classes directly or as a child of RevenueCat
, e.g. RevenueCat.Offerings
instead of Purchases.Offerings
. You can also omit the framework entirely, i.e.: just using Offerings
directly.
Our V3 SDK automatically imported StoreKit
whenever you did import Purchases
. Due to Swift limitations, our
V4 SDK doesn't do this automatically.
So if you're referencing StoreKit types directly, you might need to add
import StoreKit
in Swift, and @import StoreKit;
in Objective-C.
Step 2.1 should automatically help you convert your code into the new types. See the "New Types" section for more information on what the new types introduce.
We introduced new features for Customer Support, as well as async/await alternatives for our APIs. These are optional, but could help make your code more readable and easy to maintain.
Some additional changes include:
- Updated references of
Purchaser
toCustomer
to be more consistent across our platform - Further abstraction away from
StoreKit
with new types.
See the "New APIs" section of this guide for more details.
To better support StoreKit 2
, RevenueCat v4
introduces several new types to encapsulate data from StoreKit 1
and StoreKit 2
:
StoreProduct
: wraps aStoreKit.SKProduct
orStoreKit.Product
StoreTransaction
: wraps aStoreKit.SKPaymentTransaction
orStoreKit.Transaction
StoreProductDiscount
: wraps aStoreKit.SKProductDiscount
orStoreKit.Product.SubscriptionOffer
These types replace native StoreKit types in all public API methods that used them.
@import Purchases
is now @import RevenueCat
v3 | v4 |
---|---|
RCPurchaserInfo | RCCustomerInfo |
RCTransaction | RCStoreTransaction |
RCTransaction.productId | RCStoreTransaction.productIdentifier |
RCTransaction.revenueCatId | RCStoreTransaction.transactionIdentifier |
RCPackage.product | RCPackage.storeProduct |
(RCPurchasesErrorCode).RCOperationAlreadyInProgressError | RCOperationAlreadyInProgressForProductError |
RCPurchasesErrorDomain | RCPurchasesErrorCodeDomain |
RCBackendError | REMOVED |
RCErrorUtils | REMOVED |
RCBackendErrorDomain | REMOVED |
RCFinishableKey | REMOVED |
RCReceivePurchaserInfoBlock | REMOVED |
RCReceiveIntroEligibilityBlock | REMOVED |
RCReceiveOfferingsBlock | REMOVED |
RCReceiveProductsBlock | REMOVED |
RCPurchaseCompletedBlock | REMOVED |
RCDeferredPromotionalPurchaseBlock | REMOVED |
RCPaymentDiscountBlock | REMOVED |
RCPaymentModeNone | REMOVED |
v3 | v4 |
---|---|
purchases:didReceiveUpdatedPurchaserInfo: | purchases:receivedUpdatedCustomerInfo: |
v3 | v4 |
---|---|
purchaserInfoWithCompletion: | getCustomerInfoWithCompletion: |
invalidatePurchaserInfoCache | invalidateCustomerInfoCache |
Purchases -restoreTransactionsWithCompletion: | Purchases -restorePurchasesWithCompletion: |
Purchases -offeringsWithCompletion: | Purchases -getOfferingsWithCompletion: |
Purchases -productsWithIdentifiers:completion: | Purchases -getProductsWithIdentifiers:completion: |
Purchases -paymentDiscountForProductDiscount:product:completion: | REMOVED - Check eligibility for a discount using getPromotionalOffer:forProductDiscount:product:completion: , then pass the promotional offer directly to purchasePackage:withPromotionalOffer:completion: or purchaseProduct:withPromotionalOffer:completion: |
Purchases -purchaseProduct(:discount:) | Purchases -purchaseProduct:withPromotionalOffer:completion: |
Purchases -purchasePackage(:discount:) | Purchases -purchasePackage:withPromotionalOffer:completion: |
Purchases -createAlias: | Purchases -logIn: |
Purchases -identify: | Purchases -logIn: |
Purchases -reset: | Purchases -logOut: |
import Purchases
is now import RevenueCat
v3 | v4 |
---|---|
Purchases.Offering | Offering |
Purchases.ErrorDomain | See error handling below |
Purchases.ErrorCode.Code | See error handling below |
Purchases.Package | Package |
Purchases.PurchaserInfo | CustomerInfo |
Purchases.Transaction | StoreTransaction |
Purchases.Transaction.productId | StoreTransaction/productIdentifier |
Purchases.Transaction.revenueCatId | StoreTransaction/transactionIdentifier |
Purchases.EntitlementInfo | EntitlementInfo |
Purchases.EntitlementInfos | EntitlementInfos |
Purchases.PeriodType | PeriodType |
Purchases.Store | Store |
RCPurchaseOwnershipType | PurchaseOwnershipType |
RCAttributionNetwork | AttributionNetwork |
RCIntroEligibility | IntroEligibility |
RCIntroEligibilityStatus | IntroEligibilityStatus |
RCPaymentMode | StoreProductDiscount/PaymentMode-swift.enum |
RCPaymentModeNone | REMOVED |
Purchases.LogLevel | LogLevel |
Purchases.Offerings | Offerings |
Purchases.PackageType | PackageType |
Purchases.Errors | ErrorCode |
Purchases.ErrorCode | ErrorCode |
Package.product | Package/storeProduct |
Package.product.price: NSDecimalNumber | StoreProduct/price : Decimal |
Package.localizedIntroductoryPriceString: String | Package/localizedIntroductoryPriceString : String? |
RCDeferredPromotionalPurchaseBlock | StartPurchaseBlock |
Purchases.PurchaseCompletedBlock | PurchaseCompletedBlock |
Purchases.ReceivePurchaserInfoBlock | REMOVED |
Purchases.ReceiveOfferingsBlock | REMOVED |
Purchases.ReceiveProductsBlock | REMOVED |
Purchases.PaymentDiscountBlock | REMOVED |
Purchases.RevenueCatBackendErrorCode | REMOVED |
Purchases.ErrorCode.operationAlreadyInProgressError | RevenueCat/ErrorCode/operationAlreadyInProgressForProductError |
Purchases.ErrorUtils | REMOVED |
ReadableErrorCodeKey | REMOVED |
RCFinishableKey | REMOVED |
v3 | v4 |
---|---|
invalidatePurchaserInfoCache | Purchases/invalidateCustomerInfoCache() |
logIn(_ appUserId:, _ completion:) | Purchases/logIn(_:completion:)-4km90 |
createAlias(_ alias:, _ completion:) | Purchases/logIn(_:completion:)-4km90 |
identify(_ appUserID:, _ completion:) | Purchases/logIn(_:completion:)-4km90 |
reset(completion:) | Purchases/logOut(completion:) |
purchaserInfo(_ completion:) | Purchases/getCustomerInfo(completion:) |
offerings(_ completion:) | Purchases/getOfferings(completion:) |
products(_ productIdentifiers:, _ completion:) | Purchases/getProducts(_:completion:) |
purchaseProduct(_ product:, _ completion:) | Purchases/purchase(product:completion:) |
purchasePackage(_ package:, _ completion:) | Purchases/purchase(package:completion:) |
restoreTransactions(_ completion:) | Purchases/restorePurchases(completion:) |
syncPurchases(_ completion:) | Purchases/syncPurchases(completion:) |
paymentDiscount(for:product:completion:) | REMOVED - Get eligibility for a discount using Purchases/promotionalOffer(forProductDiscount:product:) , then pass the offer directly to Purchases/purchase(package:promotionalOffer:) or Purchases/purchase(product:promotionalOffer:) |
purchaseProduct(:discount:) | Purchases/purchase(product:promotionalOffer:completion:) |
purchasePackage(:discount:) | Purchases/purchase(package:promotionalOffer:completion:) |
v3 | v4 |
---|---|
purchases(_ purchases: Purchases, didReceiveUpdated purchaserInfo: PurchaserInfo) | RevenueCat/PurchasesDelegate/purchases(_:receivedUpdated:) |
Prior to the Swift migration, Purchases
exposed errors as NSError
s, so one could detect errors like this:
if error.domain == Purchases.ErrorDomain {
switch Purchases.ErrorCode(_nsError: error).code {
case .purchaseInvalidError: break
default: break
}
}
Starting from Version 4, this becomes much simpler:
if let error = error as? RevenueCat.ErrorCode {
switch error {
case .purchaseInvalidError: break
default: break
}
} else {
// Error is a different type
}
- All applicable methods have an async/await alternative.
Purchases/showManageSubscriptions(completion:)
: Use this method to show the subscription management for the current user. Depending on where they made the purchase and their OS version, this might take them to themanagementURL
, or open the iOS Subscription Management page.Purchases/beginRefundRequestForActiveEntitlement()
: Use this method to begin a refund request for the purchase that granted the current entitlement.Purchases/beginRefundRequest(forProduct:)
: Use this method to begin a refund request for a purchase, specifying the product identifier.Purchases/beginRefundRequest(forEntitlement:)
: Use this method to begin a refund request for a purchase, specifying the entitlement identifier.- You can now use
Purchases/customerInfoStream
to be notified whenever there's newCustomerInfo
available, as an alternative toRevenueCat/PurchasesDelegate/purchases(_:receivedUpdated:)
.
Feel free to file an issue! New RevenueCat Issue.
If you expose any Purchases objects from one target to another (see example project for what this could look like), that second target will not build due to a missing autogenerated header. Currently there is a known bug in SPM whereby Xcode either doesn't pass the RevenueCat ObjC generated interface to SPM, or SPM just doesn't integrate it. You can follow along: SR-15154.
You must manually add the autogenerated file we committed to the repository
RevenueCat-Swift.h
to your project, and #import RevenueCat-Swift.h
in your bridging header. You can see how we do this in our
SPMInstallationTests project.