Skip to content

Commit

Permalink
CustomerInfo: Sendable conformance (#1824)
Browse files Browse the repository at this point in the history
The type is now immutable, so the compiler ensures it's thread-safe.

For [CSDK-379].
Extracted from #1795 to make it easier to review this in isolation.

[CSDK-379]: https://revenuecats.atlassian.net/browse/CSDK-379?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
  • Loading branch information
NachoSoto authored Aug 22, 2022
1 parent 5c01aa3 commit 8d88328
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 17 deletions.
37 changes: 22 additions & 15 deletions Sources/Identity/CustomerInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,10 @@ import Foundation
@objc public let entitlements: EntitlementInfos

/// All *subscription* product identifiers with expiration dates in the future.
@objc public var activeSubscriptions: Set<String> { activeKeys(dates: expirationDatesByProductId) }
@objc public var activeSubscriptions: Set<String> { self.activeKeys(dates: expirationDatesByProductId) }

/// All product identifiers purchases by the user regardless of expiration.
@objc public private(set) lazy var allPurchasedProductIdentifiers: Set<String> = {
return Set(self.expirationDatesByProductId.keys)
.union(self.nonSubscriptions.map { $0.productIdentifier })
}()
@objc public let allPurchasedProductIdentifiers: Set<String>

/// Returns the latest expiration date of all products, nil if there are none.
@objc public var latestExpirationDate: Date? {
Expand Down Expand Up @@ -192,14 +189,15 @@ import Foundation
self.originalPurchaseDate = subscriber.originalPurchaseDate
self.originalApplicationVersion = subscriber.originalApplicationVersion
self.managementURL = subscriber.managementUrl

self.expirationDatesByProductId = Self.extractExpirationDates(subscriber)
self.purchaseDatesByProductId = Self.extractPurchaseDates(subscriber)
self.allPurchasedProductIdentifiers = Set(self.expirationDatesByProductId.keys)
.union(self.nonSubscriptions.map { $0.productIdentifier })
}

private lazy var expirationDatesByProductId: [String: Date?] = {
return self.extractExpirationDates()
}()
private lazy var purchaseDatesByProductId: [String: Date?] = {
return self.extractPurchaseDates()
}()
private let expirationDatesByProductId: [String: Date?]
private let purchaseDatesByProductId: [String: Date?]
}

// MARK: - Internal
Expand Down Expand Up @@ -233,6 +231,15 @@ extension CustomerInfo: RawDataContainer {

}

#if swift(>=5.7)
extension CustomerInfo: Sendable {}
#else
// `@unchecked` because:
// - `Date` is not `Sendable` until Swift 5.7
// - `URL` is not `Sendable` until Swift 5.7
extension CustomerInfo: @unchecked Sendable {}
#endif

/// `CustomerInfo`'s `Codable` implementation relies on `Data`
extension CustomerInfo: Codable {

Expand Down Expand Up @@ -317,12 +324,12 @@ private extension CustomerInfo {

func isAfterReferenceDate(date: Date) -> Bool { date.timeIntervalSince(self.requestDate) > 0 }

func extractExpirationDates() -> [String: Date?] {
return self.subscriber.subscriptions.mapValues { $0.expiresDate }
static func extractExpirationDates(_ subscriber: CustomerInfoResponse.Subscriber) -> [String: Date?] {
return subscriber.subscriptions.mapValues { $0.expiresDate }
}

func extractPurchaseDates() -> [String: Date?] {
return self.subscriber.allTransactionsByProductId.mapValues { $0.purchaseDate }
static func extractPurchaseDates(_ subscriber: CustomerInfoResponse.Subscriber) -> [String: Date?] {
return subscriber.allTransactionsByProductId.mapValues { $0.purchaseDate }
}

}
16 changes: 15 additions & 1 deletion Sources/Purchasing/EntitlementInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import Foundation
}

extension Store: CaseIterable {}
extension Store: Sendable {}

extension Store: DefaultValueProvider {

Expand All @@ -66,6 +67,7 @@ extension Store: DefaultValueProvider {
}

extension PeriodType: CaseIterable {}
extension PeriodType: Sendable {}

extension PeriodType: DefaultValueProvider {

Expand All @@ -76,7 +78,7 @@ extension PeriodType: DefaultValueProvider {
/**
The EntitlementInfo object gives you access to all of the information about the status of a user entitlement.
*/
@objc(RCEntitlementInfo) public class EntitlementInfo: NSObject {
@objc(RCEntitlementInfo) public final class EntitlementInfo: NSObject {

/**
The entitlement identifier configured in the RevenueCat dashboard
Expand Down Expand Up @@ -243,6 +245,10 @@ extension PeriodType: DefaultValueProvider {

extension EntitlementInfo: RawDataContainer {}

// @unchecked because:
// - `rawData` is `[String: Any]` which can't be `Sendable`
extension EntitlementInfo: @unchecked Sendable {}

public extension EntitlementInfo {

/// True if the user has access to this entitlement,
Expand Down Expand Up @@ -321,3 +327,11 @@ private extension EntitlementInfo {
}

}

#if swift(>=5.7)
extension EntitlementInfo.Contents: Sendable {}
#else
// `@unchecked` because:
// - `Date` is not `Sendable` until Swift 5.7
extension EntitlementInfo.Contents: @unchecked Sendable {}
#endif
4 changes: 3 additions & 1 deletion Sources/Purchasing/EntitlementInfos.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import Foundation
/**
This class contains all the entitlements associated to the user.
*/
@objc(RCEntitlementInfos) public class EntitlementInfos: NSObject {
@objc(RCEntitlementInfos) public final class EntitlementInfos: NSObject {
/**
Dictionary of all EntitlementInfo (``EntitlementInfo``) objects (active and inactive) keyed by entitlement
identifier. This dictionary can also be accessed by using an index subscript on ``EntitlementInfos``, e.g.
Expand Down Expand Up @@ -124,3 +124,5 @@ extension EntitlementInfos {
}

}

extension EntitlementInfos: Sendable {}
8 changes: 8 additions & 0 deletions Sources/Purchasing/NonSubscriptionTransaction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,11 @@ public final class NonSubscriptionTransaction: NSObject {
}

}

#if swift(>=5.7)
extension NonSubscriptionTransaction: Sendable {}
#else
// `@unchecked` because:
// - `Date` is not `Sendable` until Swift 5.7
extension NonSubscriptionTransaction: @unchecked Sendable {}
#endif
1 change: 1 addition & 0 deletions Sources/Purchasing/PurchaseOwnershipType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import Foundation
}

extension PurchaseOwnershipType: CaseIterable {}
extension PurchaseOwnershipType: Sendable {}

extension PurchaseOwnershipType: DefaultValueProvider {

Expand Down

0 comments on commit 8d88328

Please sign in to comment.