Skip to content

Commit

Permalink
Merge pull request #178 from superwall-me/develop
Browse files Browse the repository at this point in the history
v3.4.1
  • Loading branch information
yusuftor committed Sep 29, 2023
2 parents c920dc0 + e2b110e commit 62426d5
Show file tree
Hide file tree
Showing 41 changed files with 222 additions and 86 deletions.
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,18 @@

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.
- Fixes issue where transaction data wouldn't be available for those who are using a purchase controller.

## 3.4.0

### 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_<id>": "<outcome>"`. 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.
Expand Down
19 changes: 17 additions & 2 deletions Sources/SuperwallKit/Analytics/Internal Tracking/Tracking.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ extension Superwall {
let eventCreatedAt = Date()
let parameters = await TrackingLogic.processParameters(
fromTrackableEvent: event,
eventCreatedAt: eventCreatedAt,
appSessionId: dependencyContainer.appSessionManager.appSession.id
)

Expand Down Expand Up @@ -74,6 +73,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)

Expand Down Expand Up @@ -114,6 +129,6 @@ extension Superwall {

request.flags.isPaywallPresented = isPaywallPresented

internallyPresent(request, statePublisher)
await internallyPresent(request, statePublisher)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ enum TrackingLogic {

static func processParameters(
fromTrackableEvent trackableEvent: Trackable,
eventCreatedAt: Date,
appSessionId: String
) async -> TrackingParameters {
var superwallParameters = await trackableEvent.getSuperwallParameters()
Expand Down
10 changes: 8 additions & 2 deletions Sources/SuperwallKit/Debug/DebugViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -441,8 +441,10 @@ final class DebugViewController: UIViewController {
type: .presentation
)

cancellable = Superwall.shared
.internallyPresent(presentationRequest)

let publisher = PassthroughSubject<PaywallState, Never>()
cancellable = publisher
.receive(on: DispatchQueue.main)
.sink { state in
switch state {
case .presented:
Expand Down Expand Up @@ -497,6 +499,10 @@ final class DebugViewController: UIViewController {
self.activityIndicator.stopAnimating()
}
}

Task {
await Superwall.shared.internallyPresent(presentationRequest, publisher)
}
}

override func viewDidDisappear(_ animated: Bool) {
Expand Down
1 change: 1 addition & 0 deletions Sources/SuperwallKit/Delegate/SuperwallDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ final class SuperwallDelegateAdapter {
}
}

@MainActor
func subscriptionStatusDidChange(to newValue: SubscriptionStatus) {
if let swiftDelegate = swiftDelegate {
swiftDelegate.subscriptionStatusDidChange(to: newValue)
Expand Down
2 changes: 1 addition & 1 deletion Sources/SuperwallKit/Misc/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ let sdkVersion = """
*/

let sdkVersion = """
3.4.0
3.4.1
"""
67 changes: 67 additions & 0 deletions Sources/SuperwallKit/Misc/Data Structures/Queue.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//
// File.swift
//
//
// 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.
Enqueuing and dequeuing are O(1) operations.
*/
struct Queue<T> {
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? {
// 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
}

return element
}

var front: T? {
if isEmpty {
return nil
} else {
return array[head]
}
}
}
53 changes: 53 additions & 0 deletions Sources/SuperwallKit/Misc/Data Structures/SerialTaskManager.swift
Original file line number Diff line number Diff line change
@@ -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()
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ extension Superwall {

let parameters = await TrackingLogic.processParameters(
fromTrackableEvent: event,
eventCreatedAt: eventCreatedAt,
appSessionId: dependencyContainer.appSessionManager.appSession.id
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<PaywallState, Never> = .init()
) -> PaywallStatePublisher {
Task {
do {
try await checkNoPaywallAlreadyPresented(request, publisher)
_ publisher: PassthroughSubject<PaywallState, Never>
) 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
Expand Down

0 comments on commit 62426d5

Please sign in to comment.