Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## Next

- fix: session replay perforamnce improvements ([#364](https://github.com/PostHog/posthog-ios/pull/364))

## 3.28.1 - 2025-06-23

- fix: surveys decoding error ([#363](https://github.com/PostHog/posthog-ios/pull/363))
Expand Down
37 changes: 15 additions & 22 deletions PostHog/ApplicationViewLayoutPublisher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,10 @@

var onViewLayoutCallbacks: [UUID: ThrottledHandler] = [:]

static let dispatchQueue = DispatchQueue(label: "com.posthog.PostHogReplayIntegration",
target: .global(qos: .utility))

final class ThrottledHandler {
static let throttleQueue = DispatchQueue(label: "com.posthog.ThrottledHandler",
target: .global(qos: .utility))

let interval: TimeInterval
let handler: ApplicationViewLayoutHandler

Expand All @@ -110,23 +110,13 @@
}

func throttleHandler() {
let runThrottle = { [weak self] in
guard let self else { return }
let now = now()
let timeSinceLastFired = now.timeIntervalSince(lastFired)

if timeSinceLastFired >= interval {
lastFired = now
handler()
}
}
let now = now()
let timeSinceLastFired = now.timeIntervalSince(lastFired)

if Thread.isMainThread {
runThrottle()
} else {
DispatchQueue.main.async {
runThrottle()
}
if timeSinceLastFired >= interval {
lastFired = now
// notify on main
DispatchQueue.main.async(execute: handler)
}
}
}
Expand All @@ -150,9 +140,12 @@
}

func notifyHandlers() {
let handlers = registrationLock.withLock { onViewLayoutCallbacks.values }
for handler in handlers {
handler.throttleHandler()
ThrottledHandler.throttleQueue.async {
// Don't lock on main
let handlers = self.registrationLock.withLock { self.onViewLayoutCallbacks.values }
for handler in handlers {
handler.throttleHandler()
}
}
}
}
Expand Down
5 changes: 4 additions & 1 deletion PostHog/PostHogSessionManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import Foundation
registerApplicationSendEvent()
}

private let queue = DispatchQueue(label: "com.posthog.PostHogSessionManager", target: .global(qos: .utility))
private var sessionId: String?
private var sessionStartTimestamp: TimeInterval?
private var sessionActivityTimestamp: TimeInterval?
Expand Down Expand Up @@ -240,7 +241,9 @@ import Foundation
applicationEventToken = applicationEventPublisher.onApplicationEvent { [weak self] _, _ in
// update "last active" session
// we want to keep track of the idle time, so we need to maintain a timestamp on the last interactions of the user with the app. UIEvents are a good place to do so since it means that the user is actively interacting with the app (e.g not just noise background activity)
self?.touchSession()
self?.queue.async {
self?.touchSession()
}
}
#endif
}
Expand Down
20 changes: 10 additions & 10 deletions PostHog/Replay/PostHogReplayIntegration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -240,11 +240,6 @@
return
}

// always make sure we have a fresh session id as early as possible
guard let sessionId = PostHogSessionManager.shared.getSessionId(at: date) else {
return
}

// capture necessary touch information on the main thread before performing any asynchronous operations
// - this ensures that UITouch associated objects like UIView, UIWindow, or [UIGestureRecognizer] are still valid.
// - these objects may be released or erased by the system if accessed asynchronously, resulting in invalid/zeroed-out touch coordinates
Expand All @@ -253,6 +248,11 @@
}

PostHogReplayIntegration.dispatchQueue.async { [touchInfo, weak postHog = postHog] in
// always make sure we have a fresh session id as early as possible
guard let sessionId = PostHogSessionManager.shared.getSessionId(at: date) else {
return
}

// captured weakly since integration may have uninstalled by now
guard let postHog else { return }

Expand Down Expand Up @@ -312,11 +312,6 @@
windowViews.object(forKey: window) ?? ViewTreeSnapshotStatus()
}

// always make sure we have a fresh session id at correct timestamp
guard let sessionId = PostHogSessionManager.shared.getSessionId(at: timestampDate) else {
return
}

var snapshotsData: [Any] = []

if !snapshotStatus.sentMetaEvent {
Expand Down Expand Up @@ -345,6 +340,11 @@
// TODO: IncrementalSnapshot, type=2

PostHogReplayIntegration.dispatchQueue.async {
// always make sure we have a fresh session id at correct timestamp
guard let sessionId = PostHogSessionManager.shared.getSessionId(at: timestampDate) else {
return
}

var wireframes: [Any] = []
wireframes.append(wireframe.toDict())
let initialOffset = ["top": 0, "left": 0]
Expand Down
Loading