From e2096de27313ababeda1c6d1dc6ed7e718952fa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20To=CC=88r?= Date: Thu, 28 Sep 2023 15:36:42 +0800 Subject: [PATCH 1/7] Adds SerialTaskManager to make calls to register happen in serial - Implicit events also run in serial. - internallyPresent code now runs as part of the function rather than within a separate task. It doesn't return a publisher - this must be passed to it and setup beforehand. - Adds a Queue data structure to make FIFO more efficient. - All register calls are no longer wrapped in their own task blocks to avoid racing. Removed `internalPresentingViewController` from PaywallViewController as that isn't used. --- CHANGELOG.md | 2 +- .../Internal Tracking/Tracking.swift | 18 +++++- .../Debug/DebugViewController.swift | 10 +++- .../Misc/Data Structures/Queue.swift | 59 +++++++++++++++++++ .../Data Structures/SerialTaskManager.swift | 53 +++++++++++++++++ .../GetPresentationResultLogic.swift | 4 +- .../InternalPresentation.swift | 48 +++++++-------- .../InternalPresentationLogic.swift | 0 .../Operators/CheckDebuggerPresentation.swift | 0 .../CheckNoPaywallAlreadyPresented.swift | 0 .../Operators/CheckUserSubscription.swift | 0 .../Operators/ConfirmHoldoutAssignment.swift | 0 .../Operators/ConfirmPaywallAssignment.swift | 0 .../Operators/EvaluateRules.swift | 0 .../Operators/GetExperiment.swift | 0 .../Operators/GetPaywallVC.swift | 0 .../Operators/GetPresenter.swift | 0 .../Operators/LogErrors.swift | 0 .../Operators/LogPresentation.swift | 0 .../Operators/PresentPaywall.swift | 0 .../Operators/StorePresentationObjects.swift | 0 .../WaitForSubsStatusAndConfig.swift | 0 .../PaywallPresentationRequestStatus.swift | 0 .../PaywallOverrides.swift | 0 .../PresentationInfo.swift | 0 .../PresentationRequest.swift | 0 .../PaywallSkippedReason.swift | 0 .../Presentation State/PaywallState.swift | 0 .../Presentation/PublicPresentation.swift | 58 ++++++++++-------- .../PaywallViewController.swift | 6 -- .../Survey/SurveyPresentationResult.swift | 1 + Sources/SuperwallKit/Superwall.swift | 4 +- 32 files changed, 197 insertions(+), 66 deletions(-) create mode 100644 Sources/SuperwallKit/Misc/Data Structures/Queue.swift create mode 100644 Sources/SuperwallKit/Misc/Data Structures/SerialTaskManager.swift rename Sources/SuperwallKit/Paywall/Presentation/{Internal Presentation => Internal}/InternalPresentation.swift (53%) rename Sources/SuperwallKit/Paywall/Presentation/{Internal Presentation => Internal}/InternalPresentationLogic.swift (100%) rename Sources/SuperwallKit/Paywall/Presentation/{Internal Presentation => Internal}/Operators/CheckDebuggerPresentation.swift (100%) rename Sources/SuperwallKit/Paywall/Presentation/{Internal Presentation => Internal}/Operators/CheckNoPaywallAlreadyPresented.swift (100%) rename Sources/SuperwallKit/Paywall/Presentation/{Internal Presentation => Internal}/Operators/CheckUserSubscription.swift (100%) rename Sources/SuperwallKit/Paywall/Presentation/{Internal Presentation => Internal}/Operators/ConfirmHoldoutAssignment.swift (100%) rename Sources/SuperwallKit/Paywall/Presentation/{Internal Presentation => Internal}/Operators/ConfirmPaywallAssignment.swift (100%) rename Sources/SuperwallKit/Paywall/Presentation/{Internal Presentation => Internal}/Operators/EvaluateRules.swift (100%) rename Sources/SuperwallKit/Paywall/Presentation/{Internal Presentation => Internal}/Operators/GetExperiment.swift (100%) rename Sources/SuperwallKit/Paywall/Presentation/{Internal Presentation => Internal}/Operators/GetPaywallVC.swift (100%) rename Sources/SuperwallKit/Paywall/Presentation/{Internal Presentation => Internal}/Operators/GetPresenter.swift (100%) rename Sources/SuperwallKit/Paywall/Presentation/{Internal Presentation => Internal}/Operators/LogErrors.swift (100%) rename Sources/SuperwallKit/Paywall/Presentation/{Internal Presentation => Internal}/Operators/LogPresentation.swift (100%) rename Sources/SuperwallKit/Paywall/Presentation/{Internal Presentation => Internal}/Operators/PresentPaywall.swift (100%) rename Sources/SuperwallKit/Paywall/Presentation/{Internal Presentation => Internal}/Operators/StorePresentationObjects.swift (100%) rename Sources/SuperwallKit/Paywall/Presentation/{Internal Presentation => Internal}/Operators/WaitForSubsStatusAndConfig.swift (100%) rename Sources/SuperwallKit/Paywall/Presentation/{Internal Presentation => Internal}/PaywallPresentationRequestStatus.swift (100%) rename Sources/SuperwallKit/Paywall/Presentation/{Internal Presentation => Internal}/Presentation Request/PaywallOverrides.swift (100%) rename Sources/SuperwallKit/Paywall/Presentation/{Internal Presentation => Internal}/Presentation Request/PresentationInfo.swift (100%) rename Sources/SuperwallKit/Paywall/Presentation/{Internal Presentation => Internal}/Presentation Request/PresentationRequest.swift (100%) rename Sources/SuperwallKit/Paywall/Presentation/{Internal Presentation => Internal}/Presentation State/PaywallSkippedReason.swift (100%) rename Sources/SuperwallKit/Paywall/Presentation/{Internal Presentation => Internal}/Presentation State/PaywallState.swift (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4dfd4ff8..452a58e97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ The changelog for `SuperwallKit`. Also see the [releases](https://github.com/sup ### Enhancements -- Adds `sdk_version`, `sdk_version_padded`, `app_build_string`, and `app_build_string_number` to the device object for use in rules. `sdk_version` is the version of the sdk, e.g. `3.4.0`. `sdk_version_padded` is the sdk version padded with zeros for use with string comparison. For example `003.004.000`. `app_build_string` is the build of your app and `app_build_string_number` is the build of your app casted as an Int. +- Adds `sdkVersion`, `sdkVersionPadded`, `appBuildString`, and `appBuildStringNumber` to the device object for use in rules. `sdkVersion` is the version of the sdk, e.g. `3.4.0`. `sdkVersionPadded` is the sdk version padded with zeros for use with string comparison. For example `003.004.000`. `appBuildString` is the build of your app and `appBuildStringNumber` is the build of your app casted as an Int (if possible). - When you experience `no_rule_match`, the `TriggerFire` event params will specify which part of the rules didn't match in the format `"unmatched_rule_": ""`. Where `outcome` will either be `OCCURRENCE`, referring to the limit applied to a rule, or `EXPRESSION`. The `id` is the experiment id. - Adds a `touches_began` implicit trigger. By adding the `touches_began` event to a campaign, you can show a paywall the first time a user touches anywhere in your app. - Adds the ability to include a close button on a survey. diff --git a/Sources/SuperwallKit/Analytics/Internal Tracking/Tracking.swift b/Sources/SuperwallKit/Analytics/Internal Tracking/Tracking.swift index 6240f33b4..982ffef7d 100644 --- a/Sources/SuperwallKit/Analytics/Internal Tracking/Tracking.swift +++ b/Sources/SuperwallKit/Analytics/Internal Tracking/Tracking.swift @@ -74,6 +74,22 @@ extension Superwall { func handleImplicitTrigger( forEvent event: Trackable, withData eventData: EventData + ) async { + serialTaskManager.addTask { [weak self] in + guard let self = self else { + return + } + await self.internallyHandleImplicitTrigger( + forEvent: event, + withData: eventData + ) + } + } + + @MainActor + private func internallyHandleImplicitTrigger( + forEvent event: Trackable, + withData eventData: EventData ) async { let presentationInfo: PresentationInfo = .implicitTrigger(eventData) @@ -114,6 +130,6 @@ extension Superwall { request.flags.isPaywallPresented = isPaywallPresented - internallyPresent(request, statePublisher) + await internallyPresent(request, statePublisher) } } diff --git a/Sources/SuperwallKit/Debug/DebugViewController.swift b/Sources/SuperwallKit/Debug/DebugViewController.swift index 24eec73c8..8c5ef60e4 100644 --- a/Sources/SuperwallKit/Debug/DebugViewController.swift +++ b/Sources/SuperwallKit/Debug/DebugViewController.swift @@ -441,8 +441,10 @@ final class DebugViewController: UIViewController { type: .presentation ) - cancellable = Superwall.shared - .internallyPresent(presentationRequest) + + let publisher = PassthroughSubject() + cancellable = publisher + .receive(on: DispatchQueue.main) .sink { state in switch state { case .presented: @@ -497,6 +499,10 @@ final class DebugViewController: UIViewController { self.activityIndicator.stopAnimating() } } + + Task { + await Superwall.shared.internallyPresent(presentationRequest, publisher) + } } override func viewDidDisappear(_ animated: Bool) { diff --git a/Sources/SuperwallKit/Misc/Data Structures/Queue.swift b/Sources/SuperwallKit/Misc/Data Structures/Queue.swift new file mode 100644 index 000000000..b38cf5e13 --- /dev/null +++ b/Sources/SuperwallKit/Misc/Data Structures/Queue.swift @@ -0,0 +1,59 @@ +// +// File.swift +// +// +// Created by Yusuf Tör on 28/09/2023. +// + +import Foundation + +/* + First-in first-out queue (FIFO) + + New elements are added to the end of the queue. Dequeuing pulls elements from + the front of the queue. + + Enqueuing and dequeuing are O(1) operations. +*/ +struct Queue { + private var array: [T?] = [] + private var head = 0 + + var isEmpty: Bool { + // swiftlint:disable:next empty_count + return count == 0 + } + + var count: Int { + return array.count - head + } + + mutating func enqueue(_ element: T) { + array.append(element) + } + + mutating func dequeue() -> T? { + guard let element = array[guarded: head] else { + return nil + } + + array[head] = nil + head += 1 + + let percentage = Double(head) / Double(array.count) + if array.count > 50 && percentage > 0.25 { + array.removeFirst(head) + head = 0 + } + + return element + } + + var front: T? { + if isEmpty { + return nil + } else { + return array[head] + } + } +} diff --git a/Sources/SuperwallKit/Misc/Data Structures/SerialTaskManager.swift b/Sources/SuperwallKit/Misc/Data Structures/SerialTaskManager.swift new file mode 100644 index 000000000..8991a025d --- /dev/null +++ b/Sources/SuperwallKit/Misc/Data Structures/SerialTaskManager.swift @@ -0,0 +1,53 @@ +// +// File.swift +// +// +// Created by Yusuf Tör on 28/09/2023. +// + +import Foundation + +/// Serially executes tasks added to it. +final class SerialTaskManager { + private var taskQueue: Queue<() async -> Void> = Queue() + private var dispatchQueue = DispatchQueue(label: "com.superwall.serial-task-queue") + + func addTask(_ task: @escaping () async -> Void) { + dispatchQueue.async { [weak self] in + guard let self = self else { + return + } + + // Add the task to the queue + self.taskQueue.enqueue(task) + + // If there's only one task in the queue, start executing it + if self.taskQueue.count == 1 { + self.executeNextTask() + } + } + } + + private func executeNextTask() { + dispatchQueue.async { [weak self] in + guard let self = self else { + return + } + // Check if there are tasks in the queue + if taskQueue.isEmpty { + return + } + + // Get the next task from the queue + guard let nextTask = taskQueue.dequeue() else { + return + } + + Task { + await nextTask() + // After the task completes, recursively execute the next task + self.executeNextTask() + } + } + } +} diff --git a/Sources/SuperwallKit/Paywall/Presentation/Get Presentation Result/GetPresentationResultLogic.swift b/Sources/SuperwallKit/Paywall/Presentation/Get Presentation Result/GetPresentationResultLogic.swift index 9963f7556..bb58f4b0c 100644 --- a/Sources/SuperwallKit/Paywall/Presentation/Get Presentation Result/GetPresentationResultLogic.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/Get Presentation Result/GetPresentationResultLogic.swift @@ -19,8 +19,8 @@ enum GetPresentationResultLogic { return .paywallNotAvailable case .noRuleMatch: return .noRuleMatch - case .paywall(let paywall): - return .paywall(paywall) + case .paywall(let experiment): + return .paywall(experiment) } } } diff --git a/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/InternalPresentation.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal/InternalPresentation.swift similarity index 53% rename from Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/InternalPresentation.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal/InternalPresentation.swift index 9cc9f874e..1bb2e0650 100644 --- a/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/InternalPresentation.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/Internal/InternalPresentation.swift @@ -20,40 +20,32 @@ extension Superwall { /// /// - Parameters: /// - request: A presentation request of type `PresentationRequest` to feed into a presentation pipeline. - /// - paywallStatePublisher: A publisher fed into the pipeline that sends state updates. Defaults to `init()` and used by `presentAgain()` to pass in the existing state publisher. - /// - Returns: A publisher that outputs a ``PaywallState``. - @discardableResult + /// - paywallStatePublisher: A publisher fed into the pipeline that sends state updates. func internallyPresent( _ request: PresentationRequest, - _ publisher: PassthroughSubject = .init() - ) -> PaywallStatePublisher { - Task { - do { - try await checkNoPaywallAlreadyPresented(request, publisher) + _ publisher: PassthroughSubject + ) async { + do { + try await checkNoPaywallAlreadyPresented(request, publisher) - let paywallComponents = try await getPaywallComponents(request, publisher) + let paywallComponents = try await getPaywallComponents(request, publisher) - guard let presenter = paywallComponents.presenter else { - // Will never get here as an error would have already been thrown. - return - } - - try await presentPaywallViewController( - paywallComponents.viewController, - on: presenter, - unsavedOccurrence: paywallComponents.rulesOutcome.unsavedOccurrence, - debugInfo: paywallComponents.debugInfo, - request: request, - paywallStatePublisher: publisher - ) - } catch { - logErrors(from: request, error) + guard let presenter = paywallComponents.presenter else { + // Will never get here as an error would have already been thrown. + return } - } - return publisher - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() + try await presentPaywallViewController( + paywallComponents.viewController, + on: presenter, + unsavedOccurrence: paywallComponents.rulesOutcome.unsavedOccurrence, + debugInfo: paywallComponents.debugInfo, + request: request, + paywallStatePublisher: publisher + ) + } catch { + logErrors(from: request, error) + } } @MainActor diff --git a/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/InternalPresentationLogic.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal/InternalPresentationLogic.swift similarity index 100% rename from Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/InternalPresentationLogic.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal/InternalPresentationLogic.swift diff --git a/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/CheckDebuggerPresentation.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/CheckDebuggerPresentation.swift similarity index 100% rename from Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/CheckDebuggerPresentation.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/CheckDebuggerPresentation.swift diff --git a/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/CheckNoPaywallAlreadyPresented.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/CheckNoPaywallAlreadyPresented.swift similarity index 100% rename from Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/CheckNoPaywallAlreadyPresented.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/CheckNoPaywallAlreadyPresented.swift diff --git a/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/CheckUserSubscription.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/CheckUserSubscription.swift similarity index 100% rename from Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/CheckUserSubscription.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/CheckUserSubscription.swift diff --git a/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/ConfirmHoldoutAssignment.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/ConfirmHoldoutAssignment.swift similarity index 100% rename from Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/ConfirmHoldoutAssignment.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/ConfirmHoldoutAssignment.swift diff --git a/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/ConfirmPaywallAssignment.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/ConfirmPaywallAssignment.swift similarity index 100% rename from Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/ConfirmPaywallAssignment.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/ConfirmPaywallAssignment.swift diff --git a/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/EvaluateRules.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/EvaluateRules.swift similarity index 100% rename from Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/EvaluateRules.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/EvaluateRules.swift diff --git a/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/GetExperiment.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/GetExperiment.swift similarity index 100% rename from Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/GetExperiment.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/GetExperiment.swift diff --git a/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/GetPaywallVC.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/GetPaywallVC.swift similarity index 100% rename from Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/GetPaywallVC.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/GetPaywallVC.swift diff --git a/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/GetPresenter.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/GetPresenter.swift similarity index 100% rename from Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/GetPresenter.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/GetPresenter.swift diff --git a/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/LogErrors.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/LogErrors.swift similarity index 100% rename from Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/LogErrors.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/LogErrors.swift diff --git a/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/LogPresentation.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/LogPresentation.swift similarity index 100% rename from Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/LogPresentation.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/LogPresentation.swift diff --git a/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/PresentPaywall.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/PresentPaywall.swift similarity index 100% rename from Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/PresentPaywall.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/PresentPaywall.swift diff --git a/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/StorePresentationObjects.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/StorePresentationObjects.swift similarity index 100% rename from Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/StorePresentationObjects.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/StorePresentationObjects.swift diff --git a/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/WaitForSubsStatusAndConfig.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/WaitForSubsStatusAndConfig.swift similarity index 100% rename from Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/WaitForSubsStatusAndConfig.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/WaitForSubsStatusAndConfig.swift diff --git a/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/PaywallPresentationRequestStatus.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal/PaywallPresentationRequestStatus.swift similarity index 100% rename from Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/PaywallPresentationRequestStatus.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal/PaywallPresentationRequestStatus.swift diff --git a/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Presentation Request/PaywallOverrides.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal/Presentation Request/PaywallOverrides.swift similarity index 100% rename from Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Presentation Request/PaywallOverrides.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal/Presentation Request/PaywallOverrides.swift diff --git a/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Presentation Request/PresentationInfo.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal/Presentation Request/PresentationInfo.swift similarity index 100% rename from Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Presentation Request/PresentationInfo.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal/Presentation Request/PresentationInfo.swift diff --git a/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Presentation Request/PresentationRequest.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal/Presentation Request/PresentationRequest.swift similarity index 100% rename from Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Presentation Request/PresentationRequest.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal/Presentation Request/PresentationRequest.swift diff --git a/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Presentation State/PaywallSkippedReason.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal/Presentation State/PaywallSkippedReason.swift similarity index 100% rename from Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Presentation State/PaywallSkippedReason.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal/Presentation State/PaywallSkippedReason.swift diff --git a/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Presentation State/PaywallState.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal/Presentation State/PaywallState.swift similarity index 100% rename from Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Presentation State/PaywallState.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal/Presentation State/PaywallState.swift diff --git a/Sources/SuperwallKit/Paywall/Presentation/PublicPresentation.swift b/Sources/SuperwallKit/Paywall/Presentation/PublicPresentation.swift index 4499ad910..a194fd8b8 100644 --- a/Sources/SuperwallKit/Paywall/Presentation/PublicPresentation.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/PublicPresentation.swift @@ -86,9 +86,12 @@ extension Superwall { handler: PaywallPresentationHandler? = nil, feature: @escaping () -> Void ) { - Task { - await internallyRegister(event: event, params: params, handler: handler, feature: feature) - } + internallyRegister( + event: event, + params: params, + handler: handler, + feature: feature + ) } /// Registers an event which, when added to a campaign on the Superwall dashboard, can show a paywall. @@ -108,25 +111,18 @@ extension Superwall { params: [String: Any]? = nil, handler: PaywallPresentationHandler? = nil ) { - Task { - await internallyRegister(event: event, params: params, handler: handler) - } + internallyRegister(event: event, params: params, handler: handler) } - private func internallyRegister( event: String, params: [String: Any]? = nil, handler: PaywallPresentationHandler? = nil, feature completion: (() -> Void)? = nil - ) async { - let publisher = await asyncPublisher( - forEvent: event, - params: params, - paywallOverrides: nil, - isFeatureGatable: completion != nil - ) + ) { + let publisher = PassthroughSubject() publisher + .receive(on: DispatchQueue.main) .subscribe(Subscribers.Sink( receiveCompletion: { _ in }, receiveValue: { state in @@ -167,6 +163,16 @@ extension Superwall { } } )) + + serialTaskManager.addTask { + await self.asyncPublisher( + publisher, + forEvent: event, + params: params, + paywallOverrides: nil, + isFeatureGatable: completion != nil + ) + } } /// Objective-C-only convenience method. Registers an event which, when added to a campaign on the Superwall dashboard, can show a paywall. @@ -181,9 +187,7 @@ extension Superwall { /// - event: The name of the event you wish to register. @available(swift, obsoleted: 1.0) @objc public func register(event: String) { - Task { - await internallyRegister(event: event) - } + internallyRegister(event: event) } /// Objective-C-only convenience method. Registers an event which, when added to a campaign on the Superwall dashboard, can show a paywall. @@ -202,9 +206,7 @@ extension Superwall { event: String, params: [String: Any]? ) { - Task { - await internallyRegister(event: event, params: params) - } + internallyRegister(event: event, params: params) } /// Returns a publisher that registers an event which, when added to a campaign on the Superwall dashboard, can show a paywall. @@ -228,29 +230,36 @@ extension Superwall { isFeatureGatable: Bool ) -> PaywallStatePublisher { return Future { - return await self.asyncPublisher( + let publisher = PassthroughSubject() + + await self.asyncPublisher( + publisher, forEvent: event, params: params, paywallOverrides: paywallOverrides, isFeatureGatable: isFeatureGatable ) + return publisher } .flatMap { publisher in return publisher } + .receive(on: DispatchQueue.main) .eraseToAnyPublisher() } + private func asyncPublisher( + _ publisher: PassthroughSubject, forEvent event: String, params: [String: Any]? = nil, paywallOverrides: PaywallOverrides? = nil, isFeatureGatable: Bool - ) async -> PaywallStatePublisher { + ) async { do { try TrackingLogic.checkNotSuperwallEvent(event) } catch { - return Just(.presentationError(error)).eraseToAnyPublisher() + return } let trackableEvent = UserInitiatedEvent.Track( @@ -267,7 +276,6 @@ extension Superwall { isPaywallPresented: isPaywallPresented, type: .presentation ) - return internallyPresent(presentationRequest) - .eraseToAnyPublisher() + await internallyPresent(presentationRequest, publisher) } } diff --git a/Sources/SuperwallKit/Paywall/View Controller/PaywallViewController.swift b/Sources/SuperwallKit/Paywall/View Controller/PaywallViewController.swift index d554cf972..f9b88ce28 100644 --- a/Sources/SuperwallKit/Paywall/View Controller/PaywallViewController.swift +++ b/Sources/SuperwallKit/Paywall/View Controller/PaywallViewController.swift @@ -134,10 +134,6 @@ public class PaywallViewController: UIViewController, LoadingDelegate { /// `true` if there's a survey to complete and the paywall is displayed in a modal style. private var didDisableSwipeForSurvey = false - /// The presenting view controller, saved for presenting surveys from when - /// the view disappears. - private var internalPresentingViewController: UIViewController? - /// Whether the survey was shown, not shown, or in a holdout. Defaults to not shown. private var surveyPresentationResult: SurveyPresentationResult = .noShow @@ -707,10 +703,8 @@ extension PaywallViewController { presentationWillPrepare = false } - public override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - internalPresentingViewController = presentingViewController presentationDidFinish() } diff --git a/Sources/SuperwallKit/Paywall/View Controller/Survey/SurveyPresentationResult.swift b/Sources/SuperwallKit/Paywall/View Controller/Survey/SurveyPresentationResult.swift index 16b50aa9b..7f5f03f68 100644 --- a/Sources/SuperwallKit/Paywall/View Controller/Survey/SurveyPresentationResult.swift +++ b/Sources/SuperwallKit/Paywall/View Controller/Survey/SurveyPresentationResult.swift @@ -7,6 +7,7 @@ import Foundation +/// **Do not edit the cases of this as it's used in events** enum SurveyPresentationResult: String { case show case holdout diff --git a/Sources/SuperwallKit/Superwall.swift b/Sources/SuperwallKit/Superwall.swift index d15bc50e9..f98f1e26c 100644 --- a/Sources/SuperwallKit/Superwall.swift +++ b/Sources/SuperwallKit/Superwall.swift @@ -147,7 +147,6 @@ public final class Superwall: NSObject, ObservableObject { // MARK: - Non-public Properties private static var superwall: Superwall? - /// The presented paywall view controller. var paywallViewController: PaywallViewController? { return dependencyContainer.paywallManager.presentedViewController @@ -170,6 +169,9 @@ public final class Superwall: NSObject, ObservableObject { /// Handles all dependencies. let dependencyContainer: DependencyContainer + /// Used to serially execute register calls. + let serialTaskManager = SerialTaskManager() + // MARK: - Private Functions init(dependencyContainer: DependencyContainer = DependencyContainer()) { self.dependencyContainer = dependencyContainer From 489fbf517f1b3d7cfd9eab13d2945426d86ae65a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20To=CC=88r?= Date: Thu, 28 Sep 2023 15:39:30 +0800 Subject: [PATCH 2/7] Updated version number, added changelog entry --- CHANGELOG.md | 6 ++++++ Sources/SuperwallKit/Misc/Constants.swift | 2 +- SuperwallKit.podspec | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 452a58e97..48b4cb64e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ The changelog for `SuperwallKit`. Also see the [releases](https://github.com/superwall-me/Superwall-iOS/releases) on GitHub. +## 3.4.1 + +### Fixes + +- Fixes issue where multiple events registered in quick succession may not be performed in serial, resulting in unexpected paywalls. + ## 3.4.0 ### Enhancements diff --git a/Sources/SuperwallKit/Misc/Constants.swift b/Sources/SuperwallKit/Misc/Constants.swift index 7128ae2ad..cc0eb5a81 100644 --- a/Sources/SuperwallKit/Misc/Constants.swift +++ b/Sources/SuperwallKit/Misc/Constants.swift @@ -18,5 +18,5 @@ let sdkVersion = """ */ let sdkVersion = """ -3.4.0 +3.4.1 """ diff --git a/SuperwallKit.podspec b/SuperwallKit.podspec index 71c25b952..8d9ce1deb 100644 --- a/SuperwallKit.podspec +++ b/SuperwallKit.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "SuperwallKit" - s.version = "3.4.0" + s.version = "3.4.1" s.summary = "Superwall: In-App Paywalls Made Easy" s.description = "Paywall infrastructure for mobile apps :) we make things like editing your paywall and running price tests as easy as clicking a few buttons. superwall.com" From 53b53cc9f9b8f0394b9e6f8851dec763c1588950 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20To=CC=88r?= Date: Thu, 28 Sep 2023 17:29:09 +0800 Subject: [PATCH 3/7] Fixes issue where transactions weren't being found for those using purchase controllers. --- .../Internal Tracking/Tracking.swift | 1 - .../Internal Tracking/TrackingLogic.swift | 1 - .../PublicGetPresentationResult.swift | 1 - .../Presentation/PublicPresentation.swift | 19 ++++++++++--------- .../StoreKit/InternalPurchaseController.swift | 3 +++ .../Purchasing/ProductPurchaserSK1.swift | 2 -- .../Purchasing/PurchasingCoordinator.swift | 1 + .../TrackingLogicTests.swift | 10 ---------- 8 files changed, 14 insertions(+), 24 deletions(-) diff --git a/Sources/SuperwallKit/Analytics/Internal Tracking/Tracking.swift b/Sources/SuperwallKit/Analytics/Internal Tracking/Tracking.swift index 982ffef7d..75714da49 100644 --- a/Sources/SuperwallKit/Analytics/Internal Tracking/Tracking.swift +++ b/Sources/SuperwallKit/Analytics/Internal Tracking/Tracking.swift @@ -20,7 +20,6 @@ extension Superwall { let eventCreatedAt = Date() let parameters = await TrackingLogic.processParameters( fromTrackableEvent: event, - eventCreatedAt: eventCreatedAt, appSessionId: dependencyContainer.appSessionManager.appSession.id ) diff --git a/Sources/SuperwallKit/Analytics/Internal Tracking/TrackingLogic.swift b/Sources/SuperwallKit/Analytics/Internal Tracking/TrackingLogic.swift index d2b69fc02..0f7b0ef08 100644 --- a/Sources/SuperwallKit/Analytics/Internal Tracking/TrackingLogic.swift +++ b/Sources/SuperwallKit/Analytics/Internal Tracking/TrackingLogic.swift @@ -18,7 +18,6 @@ enum TrackingLogic { static func processParameters( fromTrackableEvent trackableEvent: Trackable, - eventCreatedAt: Date, appSessionId: String ) async -> TrackingParameters { var superwallParameters = await trackableEvent.getSuperwallParameters() diff --git a/Sources/SuperwallKit/Paywall/Presentation/Get Presentation Result/PublicGetPresentationResult.swift b/Sources/SuperwallKit/Paywall/Presentation/Get Presentation Result/PublicGetPresentationResult.swift index 6812f7530..03d3986fe 100644 --- a/Sources/SuperwallKit/Paywall/Presentation/Get Presentation Result/PublicGetPresentationResult.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/Get Presentation Result/PublicGetPresentationResult.swift @@ -77,7 +77,6 @@ extension Superwall { let parameters = await TrackingLogic.processParameters( fromTrackableEvent: event, - eventCreatedAt: eventCreatedAt, appSessionId: dependencyContainer.appSessionManager.appSession.id ) diff --git a/Sources/SuperwallKit/Paywall/Presentation/PublicPresentation.swift b/Sources/SuperwallKit/Paywall/Presentation/PublicPresentation.swift index a194fd8b8..b75b15751 100644 --- a/Sources/SuperwallKit/Paywall/Presentation/PublicPresentation.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/PublicPresentation.swift @@ -113,6 +113,7 @@ extension Superwall { ) { internallyRegister(event: event, params: params, handler: handler) } + private func internallyRegister( event: String, params: [String: Any]? = nil, @@ -165,12 +166,12 @@ extension Superwall { )) serialTaskManager.addTask { - await self.asyncPublisher( - publisher, + await self.trackAndPresentPaywall( forEvent: event, params: params, paywallOverrides: nil, - isFeatureGatable: completion != nil + isFeatureGatable: completion != nil, + publisher: publisher ) } } @@ -232,12 +233,12 @@ extension Superwall { return Future { let publisher = PassthroughSubject() - await self.asyncPublisher( - publisher, + await self.trackAndPresentPaywall( forEvent: event, params: params, paywallOverrides: paywallOverrides, - isFeatureGatable: isFeatureGatable + isFeatureGatable: isFeatureGatable, + publisher: publisher ) return publisher } @@ -249,12 +250,12 @@ extension Superwall { } - private func asyncPublisher( - _ publisher: PassthroughSubject, + private func trackAndPresentPaywall( forEvent event: String, params: [String: Any]? = nil, paywallOverrides: PaywallOverrides? = nil, - isFeatureGatable: Bool + isFeatureGatable: Bool, + publisher: PassthroughSubject ) async { do { try TrackingLogic.checkNotSuperwallEvent(event) diff --git a/Sources/SuperwallKit/StoreKit/InternalPurchaseController.swift b/Sources/SuperwallKit/StoreKit/InternalPurchaseController.swift index 3c5b5b576..6ad5680b6 100644 --- a/Sources/SuperwallKit/StoreKit/InternalPurchaseController.swift +++ b/Sources/SuperwallKit/StoreKit/InternalPurchaseController.swift @@ -132,6 +132,9 @@ extension InternalPurchaseController { extension InternalPurchaseController { @MainActor func purchase(product: SKProduct) async -> PurchaseResult { + await productPurchaser.coordinator.beginPurchase( + of: product.productIdentifier + ) if let purchaseController = swiftPurchaseController { return await purchaseController.purchase(product: product) } else if let purchaseController = objcPurchaseController { diff --git a/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/ProductPurchaserSK1.swift b/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/ProductPurchaserSK1.swift index 930a809f7..a92e1c555 100644 --- a/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/ProductPurchaserSK1.swift +++ b/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/ProductPurchaserSK1.swift @@ -48,8 +48,6 @@ final class ProductPurchaserSK1: NSObject { /// Purchases a product, waiting for the completion block to be fired and /// returning a purchase result. func purchase(product: SKProduct) async -> PurchaseResult { - await coordinator.beginPurchase(of: product.productIdentifier) - let task = Task { return await withCheckedContinuation { continuation in Task { diff --git a/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/PurchasingCoordinator.swift b/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/PurchasingCoordinator.swift index e054bfc13..78ef98b01 100644 --- a/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/PurchasingCoordinator.swift +++ b/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/PurchasingCoordinator.swift @@ -130,6 +130,7 @@ actor PurchasingCoordinator { lastInternalTransaction = transaction completion?(result) completion = nil + purchaseDate = nil productId = nil } } diff --git a/Tests/SuperwallKitTests/Analytics/Internal Tracking/TrackingLogicTests.swift b/Tests/SuperwallKitTests/Analytics/Internal Tracking/TrackingLogicTests.swift index 752c8c026..7fd0f142f 100644 --- a/Tests/SuperwallKitTests/Analytics/Internal Tracking/TrackingLogicTests.swift +++ b/Tests/SuperwallKitTests/Analytics/Internal Tracking/TrackingLogicTests.swift @@ -18,7 +18,6 @@ final class TrackingLogicTests: XCTestCase { // When let parameters = await TrackingLogic.processParameters( fromTrackableEvent: event, - eventCreatedAt: Date(), appSessionId: "abc" ) @@ -55,7 +54,6 @@ final class TrackingLogicTests: XCTestCase { // When let parameters = await TrackingLogic.processParameters( fromTrackableEvent: event, - eventCreatedAt: Date(), appSessionId: "abc" ) @@ -98,7 +96,6 @@ final class TrackingLogicTests: XCTestCase { // When let parameters = await TrackingLogic.processParameters( fromTrackableEvent: event, - eventCreatedAt: event.eventData!.createdAt, appSessionId: "abc" ) @@ -121,7 +118,6 @@ final class TrackingLogicTests: XCTestCase { // When let parameters = await TrackingLogic.processParameters( fromTrackableEvent: event, - eventCreatedAt: Date(), appSessionId: "abc" ) @@ -148,7 +144,6 @@ final class TrackingLogicTests: XCTestCase { // When let parameters = await TrackingLogic.processParameters( fromTrackableEvent: event, - eventCreatedAt: Date(), appSessionId: "abc" ) @@ -175,7 +170,6 @@ final class TrackingLogicTests: XCTestCase { // When let parameters = await TrackingLogic.processParameters( fromTrackableEvent: event, - eventCreatedAt: Date(), appSessionId: "abc" ) @@ -202,7 +196,6 @@ final class TrackingLogicTests: XCTestCase { // When let parameters = await TrackingLogic.processParameters( fromTrackableEvent: event, - eventCreatedAt: Date(), appSessionId: "abc" ) @@ -230,7 +223,6 @@ final class TrackingLogicTests: XCTestCase { // When let parameters = await TrackingLogic.processParameters( fromTrackableEvent: event, - eventCreatedAt: Date(), appSessionId: "abc" ) @@ -258,7 +250,6 @@ final class TrackingLogicTests: XCTestCase { // When let parameters = await TrackingLogic.processParameters( fromTrackableEvent: event, - eventCreatedAt: Date(), appSessionId: "abc" ) @@ -285,7 +276,6 @@ final class TrackingLogicTests: XCTestCase { // When let parameters = await TrackingLogic.processParameters( fromTrackableEvent: event, - eventCreatedAt: Date(), appSessionId: "abc" ) From 84b657cae74040947f80c9c9ac4a5de23eb06237 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20To=CC=88r?= Date: Thu, 28 Sep 2023 17:31:18 +0800 Subject: [PATCH 4/7] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48b4cb64e..0fe1dacdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The changelog for `SuperwallKit`. Also see the [releases](https://github.com/sup ### Fixes - Fixes issue where multiple events registered in quick succession may not be performed in serial, resulting in unexpected paywalls. +- Fixes issue where transaction data wouldn't be available for those who are using a purchase controller. ## 3.4.0 From 2014e26d78496097ea9c6b3c43898af89ef26c3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20To=CC=88r?= Date: Thu, 28 Sep 2023 17:46:25 +0800 Subject: [PATCH 5/7] Don't set purchaseDate to nil --- .../StoreKit/Transactions/Purchasing/PurchasingCoordinator.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/PurchasingCoordinator.swift b/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/PurchasingCoordinator.swift index 78ef98b01..e054bfc13 100644 --- a/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/PurchasingCoordinator.swift +++ b/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/PurchasingCoordinator.swift @@ -130,7 +130,6 @@ actor PurchasingCoordinator { lastInternalTransaction = transaction completion?(result) completion = nil - purchaseDate = nil productId = nil } } From 48ab40420ad644dc1459e26060a5f11ae5d6d472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20To=CC=88r?= Date: Fri, 29 Sep 2023 10:38:46 +0800 Subject: [PATCH 6/7] Add comments to queue data structure --- Sources/SuperwallKit/Misc/Data Structures/Queue.swift | 10 +++++++++- .../Paywall/Presentation/PublicPresentation.swift | 1 - 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Sources/SuperwallKit/Misc/Data Structures/Queue.swift b/Sources/SuperwallKit/Misc/Data Structures/Queue.swift index b38cf5e13..87f254103 100644 --- a/Sources/SuperwallKit/Misc/Data Structures/Queue.swift +++ b/Sources/SuperwallKit/Misc/Data Structures/Queue.swift @@ -4,12 +4,14 @@ // // Created by Yusuf Tör on 28/09/2023. // +// https://github.com/kodecocodes/swift-algorithm-club/blob/master/Queue/README.markdown#a-more-efficient-queue import Foundation -/* +/** First-in first-out queue (FIFO) + Note: This isn't threadsafe. New elements are added to the end of the queue. Dequeuing pulls elements from the front of the queue. @@ -33,14 +35,20 @@ struct Queue { } mutating func dequeue() -> T? { + // Get element at head of queue guard let element = array[guarded: head] else { return nil } + // Replace the element with nil and increment the head array[head] = nil head += 1 + // Calculate the percentage of the array that is nil let percentage = Double(head) / Double(array.count) + + // If more than 25% of the queue is nil, chop off the head and reset + // head to 0 if array.count > 50 && percentage > 0.25 { array.removeFirst(head) head = 0 diff --git a/Sources/SuperwallKit/Paywall/Presentation/PublicPresentation.swift b/Sources/SuperwallKit/Paywall/Presentation/PublicPresentation.swift index b75b15751..937a7ddfc 100644 --- a/Sources/SuperwallKit/Paywall/Presentation/PublicPresentation.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/PublicPresentation.swift @@ -249,7 +249,6 @@ extension Superwall { .eraseToAnyPublisher() } - private func trackAndPresentPaywall( forEvent event: String, params: [String: Any]? = nil, From e2b110e3a281501562960c0fe36bb9c75ec3ac20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20To=CC=88r?= Date: Fri, 29 Sep 2023 10:43:38 +0800 Subject: [PATCH 7/7] Enforce subscriptionStatusDidChange is performed on the MainActor --- Sources/SuperwallKit/Delegate/SuperwallDelegate.swift | 1 + Sources/SuperwallKit/Delegate/SuperwallDelegateAdapter.swift | 1 + Sources/SuperwallKit/Superwall.swift | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/SuperwallKit/Delegate/SuperwallDelegate.swift b/Sources/SuperwallKit/Delegate/SuperwallDelegate.swift index 24e1ba140..a1aaaf497 100644 --- a/Sources/SuperwallKit/Delegate/SuperwallDelegate.swift +++ b/Sources/SuperwallKit/Delegate/SuperwallDelegate.swift @@ -27,6 +27,7 @@ public protocol SuperwallDelegate: AnyObject { /// /// - Parameters: /// - newValue: The new value of ``Superwall/subscriptionStatus``. + @MainActor func subscriptionStatusDidChange(to newValue: SubscriptionStatus) /// Called whenever an internal analytics event is tracked. diff --git a/Sources/SuperwallKit/Delegate/SuperwallDelegateAdapter.swift b/Sources/SuperwallKit/Delegate/SuperwallDelegateAdapter.swift index e1189e907..30f7051c5 100644 --- a/Sources/SuperwallKit/Delegate/SuperwallDelegateAdapter.swift +++ b/Sources/SuperwallKit/Delegate/SuperwallDelegateAdapter.swift @@ -86,6 +86,7 @@ final class SuperwallDelegateAdapter { } } + @MainActor func subscriptionStatusDidChange(to newValue: SubscriptionStatus) { if let swiftDelegate = swiftDelegate { swiftDelegate.subscriptionStatusDidChange(to: newValue) diff --git a/Sources/SuperwallKit/Superwall.swift b/Sources/SuperwallKit/Superwall.swift index f98f1e26c..4f3b9539b 100644 --- a/Sources/SuperwallKit/Superwall.swift +++ b/Sources/SuperwallKit/Superwall.swift @@ -238,9 +238,9 @@ public final class Superwall: NSObject, ObservableObject { return } self.dependencyContainer.storage.save(newValue, forType: ActiveSubscriptionStatus.self) - self.dependencyContainer.delegateAdapter.subscriptionStatusDidChange(to: newValue) Task { + await self.dependencyContainer.delegateAdapter.subscriptionStatusDidChange(to: newValue) let event = InternalSuperwallEvent.SubscriptionStatusDidChange(subscriptionStatus: newValue) await self.track(event) }