Skip to content

Commit 6ad6fdb

Browse files
committed
Add context-based function functions
1 parent fb9fe0a commit 6ad6fdb

File tree

5 files changed

+68
-45
lines changed

5 files changed

+68
-45
lines changed

RELEASE_NOTES.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@ This version makes StoreKitPlus use Swift 6.
1111

1212
### ‼️ Important information
1313

14-
As a result of the Swift 6 transition, and due to data race problems, the store services no longer takes a context and keeps it in sync. This must be explicitly handled by the caller.
14+
As a result of the Swift 6 transition, and due to data race problems, the store services no longer takes a context and keeps it in sync. This must be explicitly handled by the caller, for instance using the new context-based functions.
1515

1616
Furthermore, the service model is drastically simplified in this version. Instead of having multiple service types, `StoreService` handles it all.
1717

1818
### ✨ Features
1919

2020
* `StoreService` has new `.standard` shorthands.
21+
* `StoreService` has new `context`-based function versions.
2122
* `StandardStoreService` no longer accepts a context, and will no longer keep it in sync.
2223

2324
### 🗑️ Deprecations

Sources/StoreKitPlus/Data/Persisted.swift

-40
This file was deleted.

Sources/StoreKitPlus/Services/StandardStoreService.swift

+3-2
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,10 @@ open class StandardStoreService: StoreService {
7070
try await Product.products(for: productIds)
7171
}
7272

73+
@discardableResult
7374
open func purchase(
7475
_ product: Product
75-
) async throws -> Product.PurchaseResult {
76+
) async throws -> (Product.PurchaseResult, Transaction?) {
7677
#if os(visionOS)
7778
throw StoreServiceError.unsupportedPlatform("This purchase operation is not supported in visionOS: Use @Environment(\\.purchase) instead.")
7879
#else
@@ -83,7 +84,7 @@ open class StandardStoreService: StoreService {
8384
case .userCancelled: break
8485
@unknown default: break
8586
}
86-
return result
87+
return (result, nil)
8788
#endif
8889
}
8990

Sources/StoreKitPlus/Services/StoreService.swift

+32-2
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ public protocol StoreService {
2121
func getProducts() async throws -> [Product]
2222

2323
/// Purchase a certain product.
24+
@discardableResult
2425
func purchase(
2526
_ product: Product
26-
) async throws -> Product.PurchaseResult
27+
) async throws -> (Product.PurchaseResult, Transaction?)
2728

2829
/// Restore previous purchases.
2930
@discardableResult
@@ -35,11 +36,40 @@ public protocol StoreService {
3536
) async throws
3637
}
3738

39+
public extension StoreService {
40+
41+
/// Purchase a certain product.
42+
///
43+
/// This function will sync the result to the context.
44+
@discardableResult
45+
func purchase(
46+
_ product: Product,
47+
syncWith context: StoreContext
48+
) async throws -> (Product.PurchaseResult, Transaction?) {
49+
let result = try await purchase(product)
50+
if let transaction = result.1 {
51+
await context.updatePurchaseTransactions(with: transaction)
52+
}
53+
return result
54+
}
55+
56+
/// Restore previous purchases.
57+
///
58+
/// This function will sync the result to the context.
59+
@discardableResult
60+
func restorePurchases(
61+
syncWith context: StoreContext
62+
) async throws -> [Transaction] {
63+
let result = try await restorePurchases()
64+
await context.updatePurchaseTransactions(result)
65+
return result
66+
}
67+
}
68+
3869
public extension StoreService {
3970

4071
@available(*, deprecated, message: "You have to pass in a context now.")
4172
func syncStoreData() async throws {
4273
try await syncStoreData(to: .init())
4374
}
4475
}
45-

Sources/StoreKitPlus/StoreContext.swift

+31
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,34 @@ private extension StoreContext {
125125

126126
static func key(_ name: String) -> String { "com.danielsaidi.storekitplus.\(name)" }
127127
}
128+
129+
/// This property wrapper automatically persists a new value
130+
/// to user defaults.
131+
@propertyWrapper
132+
struct Persisted<T: Codable> {
133+
134+
init(
135+
key: String,
136+
store: UserDefaults = .standard,
137+
defaultValue: T) {
138+
self.key = key
139+
self.store = store
140+
self.defaultValue = defaultValue
141+
}
142+
143+
private let key: String
144+
private let store: UserDefaults
145+
private let defaultValue: T
146+
147+
var wrappedValue: T {
148+
get {
149+
guard let data = store.object(forKey: key) as? Data else { return defaultValue }
150+
let value = try? JSONDecoder().decode(T.self, from: data)
151+
return value ?? defaultValue
152+
}
153+
set {
154+
let data = try? JSONEncoder().encode(newValue)
155+
store.set(data, forKey: key)
156+
}
157+
}
158+
}

0 commit comments

Comments
 (0)