Skip to content

Commit 265a982

Browse files
authored
Merge pull request #285 from superwall/v4-develop
4.0.0-beta.4
2 parents 1db3260 + eb33eb6 commit 265a982

File tree

9 files changed

+260
-335
lines changed

9 files changed

+260
-335
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
The changelog for `SuperwallKit`. Also see the [releases](https://github.com/superwall/Superwall-iOS/releases) on GitHub.
44

5+
## 4.0.0-beta.4
6+
7+
### Fixes
8+
9+
- Fixes a crash that was caused by a concurrency issue.
10+
511
## 4.0.0-beta.3
612

713
### Breaking Changes

Examples/Advanced/Advanced/HomeView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ struct HomeView: View {
9393
ZStack {
9494
switch page {
9595
case .nonGated:
96-
Text("Non gated feature launched")
96+
Text("Non-gated feature launched")
9797
case .pro:
9898
Text("Pro feature launched")
9999
case .diamond:

Sources/SuperwallKit/Graveyard/SuperwallGraveyard.swift

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,6 @@ extension Superwall {
142142
}
143143

144144
@available(*, unavailable, renamed: "SuperwallPlacementInfo")
145-
@objc(SWKSuperwallEventInfo)
146-
@objcMembers
147145
public final class SuperwallEventInfo: NSObject {}
148146

149147
extension SuperwallDelegate {
@@ -152,12 +150,9 @@ extension SuperwallDelegate {
152150
}
153151

154152
@available(*, unavailable, renamed: "ProductStore")
155-
@objc(SWKStore)
156153
public enum Store: Int {
157154
case appStore
158155
}
159156

160157
@available(*, unavailable, renamed: "Product")
161-
@objc(SWKProductInfo)
162-
@objcMembers
163158
public final class ProductInfo: NSObject, Codable, Sendable {}

Sources/SuperwallKit/Misc/Constants.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,5 @@ let sdkVersion = """
1818
*/
1919

2020
let sdkVersion = """
21-
4.0.0-beta.3
21+
4.0.0-beta.4
2222
"""
Lines changed: 245 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,273 @@
11
//
2-
// ProductType.swift
3-
// Superwall
2+
// File.swift
43
//
5-
// Created by Yusuf Tör on 01/03/2022.
4+
//
5+
// Created by Yusuf Tör on 29/03/2024.
66
//
77

88
import Foundation
99

10-
/// The type of product.
11-
@objc(SWKProductType)
12-
public enum ProductType: Int, Codable, Sendable {
13-
/// The primary product of the paywall.
14-
case primary
15-
16-
/// The secondary product of the paywall.
17-
case secondary
18-
19-
/// The tertiary product of the paywall.
20-
case tertiary
10+
/// An enum whose types specify the store which the product belongs to.
11+
@objc(SWKProductStore)
12+
public enum ProductStore: Int, Codable, Sendable {
13+
/// An Apple App Store product.
14+
case appStore
2115

22-
enum InternalProductType: String, Codable {
23-
case primary
24-
case secondary
25-
case tertiary
16+
enum CodingKeys: String, CodingKey {
17+
case appStore = "APP_STORE"
2618
}
2719

2820
public func encode(to encoder: Encoder) throws {
2921
var container = encoder.singleValueContainer()
30-
3122
switch self {
32-
case .primary:
33-
try container.encode(InternalProductType.primary.rawValue)
34-
case .secondary:
35-
try container.encode(InternalProductType.secondary.rawValue)
36-
case .tertiary:
37-
try container.encode(InternalProductType.tertiary.rawValue)
23+
case .appStore:
24+
try container.encode(CodingKeys.appStore.rawValue)
3825
}
3926
}
4027

4128
public init(from decoder: Decoder) throws {
4229
let container = try decoder.singleValueContainer()
4330
let rawValue = try container.decode(String.self)
44-
guard let internalProductType = InternalProductType(rawValue: rawValue) else {
31+
let type = CodingKeys(rawValue: rawValue)
32+
switch type {
33+
case .appStore:
34+
self = .appStore
35+
case .none:
36+
throw DecodingError.valueNotFound(
37+
String.self,
38+
.init(
39+
codingPath: [],
40+
debugDescription: "Unsupported product store type."
41+
)
42+
)
43+
}
44+
}
45+
}
46+
47+
/// An Apple App Store product.
48+
@objc(SWKAppStoreProduct)
49+
@objcMembers
50+
public final class AppStoreProduct: NSObject, Codable, Sendable {
51+
/// The bundleId that the product is associated with
52+
let bundleId: String?
53+
54+
/// The store the product belongs to.
55+
let store: ProductStore
56+
57+
/// The product identifier.
58+
public let id: String
59+
60+
enum CodingKeys: String, CodingKey {
61+
case bundleId
62+
case id = "productIdentifier"
63+
case store
64+
}
65+
66+
init(
67+
store: ProductStore = .appStore,
68+
id: String
69+
) {
70+
self.bundleId = Bundle.main.bundleIdentifier
71+
self.store = store
72+
self.id = id
73+
}
74+
75+
public func encode(to encoder: any Encoder) throws {
76+
var container = encoder.container(keyedBy: CodingKeys.self)
77+
try container.encode(id, forKey: .id)
78+
try container.encode(store, forKey: .store)
79+
try container.encodeIfPresent(bundleId, forKey: .bundleId)
80+
}
81+
82+
public init(from decoder: any Decoder) throws {
83+
let container = try decoder.container(keyedBy: CodingKeys.self)
84+
self.id = try container.decode(String.self, forKey: .id)
85+
self.store = try container.decode(ProductStore.self, forKey: .store)
86+
87+
// If the bundle ID is present, and it's not equal to the bundle
88+
// ID of the app, it gets ignored.
89+
let bundleId = try container.decodeIfPresent(String.self, forKey: .bundleId)
90+
if let bundleId = bundleId,
91+
bundleId != Bundle.main.bundleIdentifier {
4592
throw DecodingError.typeMismatch(
46-
InternalProductType.self,
47-
.init(
48-
codingPath: [],
49-
debugDescription: "Didn't find a primary, secondary, or tertiary product type."
50-
)
93+
String.self,
94+
.init(
95+
codingPath: [],
96+
debugDescription: "The bundle id of the product didn't match the bundle id of the app."
97+
)
5198
)
5299
}
53-
switch internalProductType {
54-
case .primary:
55-
self = .primary
56-
case .secondary:
57-
self = .secondary
58-
case .tertiary:
59-
self = .tertiary
100+
self.bundleId = bundleId
101+
super.init()
102+
}
103+
104+
public override func isEqual(_ object: Any?) -> Bool {
105+
guard let other = object as? AppStoreProduct else {
106+
return false
60107
}
108+
return bundleId == other.bundleId
109+
&& store == other.store
110+
&& id == other.id
111+
}
112+
113+
public override var hash: Int {
114+
var hasher = Hasher()
115+
hasher.combine(bundleId)
116+
hasher.combine(store)
117+
hasher.combine(id)
118+
return hasher.finalize()
61119
}
62120
}
63121

64-
// MARK: - CustomStringConvertible
65-
extension ProductType: CustomStringConvertible {
66-
public var description: String {
67-
switch self {
68-
case .primary:
69-
return InternalProductType.primary.rawValue
70-
case .secondary:
71-
return InternalProductType.secondary.rawValue
72-
case .tertiary:
73-
return InternalProductType.tertiary.rawValue
122+
/// An objc-only type that specifies a store and a product.
123+
@objc(SWKStoreProductAdapter)
124+
@objcMembers
125+
public final class StoreProductAdapterObjc: NSObject, Codable, Sendable {
126+
/// The store associated with the product.
127+
public let store: ProductStore
128+
129+
/// The App Store product. This is non-nil if `store` is
130+
/// `appStore`.
131+
public let appStoreProduct: AppStoreProduct?
132+
133+
init(
134+
store: ProductStore,
135+
appStoreProduct: AppStoreProduct?
136+
) {
137+
self.store = store
138+
self.appStoreProduct = appStoreProduct
139+
}
140+
}
141+
142+
/// The product in the paywall.
143+
@objc(SWKProduct)
144+
@objcMembers
145+
public final class Product: NSObject, Codable, Sendable {
146+
/// The type of store and its associated product.
147+
public enum StoreProductType: Codable, Sendable, Hashable {
148+
case appStore(AppStoreProduct)
149+
}
150+
151+
private enum CodingKeys: String, CodingKey {
152+
case name = "referenceName"
153+
case storeProduct
154+
case entitlements
155+
}
156+
157+
/// The name of the product in the editor.
158+
///
159+
/// This is optional because products can also be decoded from outside
160+
/// of a paywall.
161+
public let name: String?
162+
163+
/// The type of product
164+
public let type: StoreProductType
165+
166+
/// Convenience variable that accesses the product's identifier.
167+
public var id: String {
168+
switch type {
169+
case .appStore(let product):
170+
return product.id
74171
}
75172
}
173+
174+
/// The entitlement associated with the product.
175+
public let entitlements: Set<Entitlement>
176+
177+
/// The objc-only type of product.
178+
@objc(adapter)
179+
public let objcAdapter: StoreProductAdapterObjc
180+
181+
init(
182+
name: String?,
183+
type: StoreProductType,
184+
entitlements: Set<Entitlement>
185+
) {
186+
self.name = name
187+
self.type = type
188+
self.entitlements = entitlements
189+
190+
switch type {
191+
case .appStore(let product):
192+
objcAdapter = .init(
193+
store: .appStore,
194+
appStoreProduct: product
195+
)
196+
}
197+
}
198+
199+
public func encode(to encoder: Encoder) throws {
200+
var container = encoder.container(keyedBy: CodingKeys.self)
201+
202+
try container.encodeIfPresent(name, forKey: .name)
203+
204+
try container.encode(entitlements, forKey: .entitlements)
205+
206+
switch type {
207+
case .appStore(let product):
208+
try container.encode(product, forKey: .storeProduct)
209+
}
210+
}
211+
212+
public required init(from decoder: Decoder) throws {
213+
let container = try decoder.container(keyedBy: CodingKeys.self)
214+
name = try container.decodeIfPresent(String.self, forKey: .name)
215+
216+
// These will throw an error if the StoreProduct is not an AppStoreProduct or if the
217+
// entitlement type is not `SERVICE_LEVEL`, which must be caught in a `Throwable` and
218+
// ignored in the paywall object.
219+
entitlements = try container.decode(Set<Entitlement>.self, forKey: .entitlements)
220+
let storeProduct = try container.decode(AppStoreProduct.self, forKey: .storeProduct)
221+
type = .appStore(storeProduct)
222+
objcAdapter = .init(store: .appStore, appStoreProduct: storeProduct)
223+
}
224+
225+
public override func isEqual(_ object: Any?) -> Bool {
226+
guard let other = object as? Product else {
227+
return false
228+
}
229+
return name == other.name
230+
&& type == other.type
231+
&& entitlements == other.entitlements
232+
}
233+
234+
public override var hash: Int {
235+
var hasher = Hasher()
236+
hasher.combine(name)
237+
hasher.combine(type)
238+
hasher.combine(entitlements)
239+
return hasher.finalize()
240+
}
241+
}
242+
243+
struct TemplatingProductItem: Encodable {
244+
let name: String
245+
let productId: String
246+
247+
private enum CodingKeys: String, CodingKey {
248+
case product
249+
case productId
250+
}
251+
252+
static func create(from productItems: [Product]) -> [TemplatingProductItem] {
253+
return productItems.compactMap {
254+
guard let name = $0.name else {
255+
return nil
256+
}
257+
return TemplatingProductItem(
258+
name: name,
259+
productId: $0.id
260+
)
261+
}
262+
}
263+
264+
func encode(to encoder: Encoder) throws {
265+
var container = encoder.container(keyedBy: CodingKeys.self)
266+
267+
// Encode name as "product" for templating
268+
try container.encode(name, forKey: .product)
269+
270+
// Encode product ID as "productId" for templating
271+
try container.encode(productId, forKey: .productId)
272+
}
76273
}

0 commit comments

Comments
 (0)