Skip to content

Commit

Permalink
RUMM-1064 AppStateListener.shared is added
Browse files Browse the repository at this point in the history
  • Loading branch information
buranmert committed Mar 12, 2021
1 parent e80713d commit 8c716a5
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 67 deletions.
36 changes: 23 additions & 13 deletions Sources/Datadog/Core/System/AppStateListener.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -112,12 +111,23 @@ 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: [],
finalDate: currentState.date
)
let nc = NotificationCenter.default
Expand All @@ -128,13 +138,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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,22 +35,6 @@ class AppStateHistoryTests: XCTestCase {
XCTAssertEqual(history.foregroundDuration, 3.0)
}

func testForegroundDurationWithWrongOrderedChanges() {
let startDate = Date.mockDecember15th2019At10AMUTC()
let history = AppStateHistory(
initialState: .init(isActive: true, date: startDate),
changes: [
.init(isActive: true, date: startDate + 4.0),
.init(isActive: false, date: startDate + 3.0),
.init(isActive: false, date: startDate + 1.0),
.init(isActive: true, date: startDate + 2.0)
],
finalDate: startDate + 5.0
)

XCTAssertEqual(history.foregroundDuration, 3.0)
}

func testForegroundDurationWithMissingChange() {
let startDate = Date.mockDecember15th2019At10AMUTC()
let history = AppStateHistory(
Expand Down Expand Up @@ -105,22 +89,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)
}
Expand Down Expand Up @@ -181,36 +181,17 @@ class AppStateListenerTests: XCTestCase {
let listener = AppStateListener()
DispatchQueue.concurrentPerform(iterations: 10_000) { iteration in
// write
let nc = NotificationCenter.default
nc.post(
name: (Bool.random() ?
UIApplication.willResignActiveNotification :
UIApplication.didBecomeActiveNotification),
object: nil
)
if iteration < 1_000 {
let nc = NotificationCenter.default
nc.post(
name: (Bool.random() ?
UIApplication.willResignActiveNotification :
UIApplication.didBecomeActiveNotification),
object: nil
)
}
// read
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..<iterationCount).map { _ in AppStateListener() }
DispatchQueue.concurrentPerform(iterations: iterationCount) { iteration in
// write
let nc = NotificationCenter.default
nc.post(
name: (Bool.random() ?
UIApplication.willResignActiveNotification :
UIApplication.didBecomeActiveNotification),
object: nil
)
// read
XCTAssertFalse(listeners[iteration].history.changes.isEmpty)
}
}
}

0 comments on commit 8c716a5

Please sign in to comment.