diff --git a/Sources/Datadog/Core/System/AppStateListener.swift b/Sources/Datadog/Core/System/AppStateListener.swift index f7907431eb..b67129b102 100644 --- a/Sources/Datadog/Core/System/AppStateListener.swift +++ b/Sources/Datadog/Core/System/AppStateListener.swift @@ -20,7 +20,7 @@ internal struct AppStateHistory: Equatable { } var initialState: Snapshot - var changes: [Snapshot] + private(set) var changes: [Snapshot] // NOTE: RUMM-1064 changes.last.date > finalDate case isn't handled as not realistic var finalDate: Date var finalState: Snapshot { @@ -30,6 +30,11 @@ internal struct AppStateHistory: Equatable { ) } + mutating func add(change: Snapshot) { + changes.append(change) + changes.sort() + } + /// Limits or extrapolates app state history to the given range /// This is useful when you record between 0...3t but you are concerned of t...2t only /// - Parameter range: if outside of `initialState` and `finalState`, it extrapolates; otherwise it limits @@ -51,7 +56,7 @@ internal struct AppStateHistory: Equatable { var foregroundDuration: TimeInterval { var duration: TimeInterval = 0.0 var lastActiveStartDate: Date? - let allEvents = [initialState] + changes.sorted() + [finalState] + let allEvents = [initialState] + changes + [finalState] for event in allEvents { if let startDate = lastActiveStartDate { duration += event.date.timeIntervalSince(startDate) @@ -95,13 +100,7 @@ internal protocol AppStateListening { internal class AppStateListener: AppStateListening { typealias Snapshot = AppStateHistory.Snapshot - private static var isActive: Bool { - return UIApplication.managedShared?.applicationState == .active - } - - private static func currentState(with date: Date) -> Snapshot { - return Snapshot(isActive: AppStateListener.isActive, date: date) - } + static var shared = AppStateListener() private let dateProvider: DateProvider private var _appStateHistory: AppStateHistory @@ -112,9 +111,21 @@ internal class AppStateListener: AppStateListening { return _appStateHistory } + private static var isAppActive: Bool { + if Thread.isMainThread { + return UIApplication.managedShared?.applicationState == .active + } else { + return DispatchQueue.main.sync { + UIApplication.managedShared?.applicationState == .active + } + } + } + init(dateProvider: DateProvider = SystemDateProvider()) { self.dateProvider = dateProvider - let currentState = AppStateListener.currentState(with: dateProvider.currentDate()) + + let isAppActive = AppStateListener.isAppActive + let currentState = Snapshot(isActive: isAppActive, date: dateProvider.currentDate()) _appStateHistory = AppStateHistory( initialState: currentState, changes: [], @@ -128,13 +139,13 @@ internal class AppStateListener: AppStateListening { @objc private func appWillResignActive() { objc_sync_enter(self) - _appStateHistory.changes.append(Snapshot(isActive: false, date: dateProvider.currentDate())) + _appStateHistory.add(change: Snapshot(isActive: false, date: dateProvider.currentDate())) objc_sync_exit(self) } @objc private func appDidBecomeActive() { objc_sync_enter(self) - _appStateHistory.changes.append(Snapshot(isActive: true, date: dateProvider.currentDate())) + _appStateHistory.add(change: Snapshot(isActive: true, date: dateProvider.currentDate())) objc_sync_exit(self) } } diff --git a/Sources/Datadog/URLSessionAutoInstrumentation/Interception/TaskInterception.swift b/Sources/Datadog/URLSessionAutoInstrumentation/Interception/TaskInterception.swift index 25ae1850a4..8bae242a0d 100644 --- a/Sources/Datadog/URLSessionAutoInstrumentation/Interception/TaskInterception.swift +++ b/Sources/Datadog/URLSessionAutoInstrumentation/Interception/TaskInterception.swift @@ -27,7 +27,7 @@ internal class TaskInterception { init( request: URLRequest, isFirstParty: Bool, - appStateListener: AppStateListening = AppStateListener() + appStateListener: AppStateListening = AppStateListener.shared ) { self.identifier = UUID() self.request = request diff --git a/Tests/DatadogTests/Datadog/URLSessionAutoInstrumentation/Interception/AppStateListenerTests.swift b/Tests/DatadogTests/Datadog/URLSessionAutoInstrumentation/Interception/AppStateListenerTests.swift index 84ac993c4d..e0f36b88ff 100644 --- a/Tests/DatadogTests/Datadog/URLSessionAutoInstrumentation/Interception/AppStateListenerTests.swift +++ b/Tests/DatadogTests/Datadog/URLSessionAutoInstrumentation/Interception/AppStateListenerTests.swift @@ -105,22 +105,38 @@ class AppStateHistoryTests: XCTestCase { func testLimitingWithChanges() { let startDate = Date.mockDecember15th2019At10AMUTC() - let history = AppStateHistory( + let firstChanges = (0...100).map { _ in + AppStateHistory.Snapshot( + isActive: Bool.random(), + date: startDate + TimeInterval.random(in: 0...1_000) + ) + } + let lastChanges = (0...100).map { _ in + AppStateHistory.Snapshot( + isActive: Bool.random(), + date: startDate + TimeInterval.random(in: 2_000...3_000) + ) + } + var history = AppStateHistory( initialState: .init(isActive: true, date: startDate), - changes: [ - .init(isActive: false, date: startDate + 3.0), - .init(isActive: true, date: startDate + 6.0), - ], + changes: firstChanges + lastChanges, finalDate: startDate + 9.0 ) + history.add(change: .init(isActive: true, date: startDate + 1_200)) + history.add(change: .init(isActive: false, date: startDate + 1_500)) + history.add(change: .init(isActive: true, date: startDate + 1_700)) + let limitedHistory = history.take( - between: (startDate + 5.0)...(startDate + 10.0) + between: (startDate + 1_250)...(startDate + 1_750) ) let expectedHistory = AppStateHistory( - initialState: .init(isActive: false, date: startDate + 5.0), - changes: [.init(isActive: true, date: startDate + 6.0)], - finalDate: startDate + 10.0 + initialState: .init(isActive: true, date: startDate + 1_250), + changes: [ + .init(isActive: false, date: startDate + 1_500), + .init(isActive: true, date: startDate + 1_700), + ], + finalDate: startDate + 1_750 ) XCTAssertEqual(limitedHistory, expectedHistory) } @@ -192,25 +208,4 @@ class AppStateListenerTests: XCTestCase { XCTAssertFalse(listener.history.changes.isEmpty) } } - - // objc_sync 100 -> 0.046sec - // objc_sync 1_000 -> 8.430sec - // DispatchQueue 100 -> 0.340sec - // DispatchQueue 1_000 -> 8.630sec - func testWhenManyAppStateListenersAreCalledFromDifferentThreads_thenTheyWork() { - let iterationCount = 100 - let listeners = (0..