Skip to content

Commit a1f9f91

Browse files
committed
Refactor UIHangeDetector
1 parent a7a7b21 commit a1f9f91

File tree

4 files changed

+33
-25
lines changed

4 files changed

+33
-25
lines changed

Demo/UIHangDetectorDemo/AppDelegate.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
77
var window: UIWindow?
88

99
private let hangDetector = UIHangDetector(
10-
warningCriteria: 500(.milliseconds),
11-
criticalCriteria: 1(.seconds),
10+
warningCriteria: 600(.milliseconds),
11+
criticalCriteria: 1000(.milliseconds),
1212
healthSignalInterval: 500(.milliseconds),
1313
healthSignalCheckInterval: 100(.milliseconds)
1414
)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import Foundation
2+
import Combine
3+
4+
internal extension AnyPublisher where Failure == Never {
5+
init(_ output: Output) {
6+
self = Optional.Publisher(output)
7+
.compactMap { $0 }
8+
.eraseToAnyPublisher()
9+
}
10+
}

Sources/UIHangDetector/HealthChecker.swift renamed to Sources/UIHangDetector/RunLoopHealthChecker.swift

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import Foundation
22
import Combine
33

4-
internal final class HealthChecker {
4+
internal final class RunLoopHealthChecker {
55
private let healthSubject = PassthroughSubject<Health, Never>()
6-
private let healthSignalSubject = CurrentValueSubject<Date, Never>(Date.distantPast)
76

87
private var timerThread: Thread?
98
private var subscription: AnyCancellable?
109

10+
private let target: RunLoop
1111
private let warningCriteria: TimeInterval
1212
private let criticalCriteria: TimeInterval
13+
private let healthSignalInterval: TimeInterval
1314
private let healthSignalCheckInterval: TimeInterval
1415

1516
var healthStream: AnyPublisher<Health, Never> {
@@ -19,12 +20,16 @@ internal final class HealthChecker {
1920
}
2021

2122
init(
23+
target: RunLoop,
2224
warningCriteria: Duration,
2325
criticalCriteria: Duration,
26+
healthSignalInterval: Duration,
2427
healthSignalCheckInterval: Duration
2528
) {
29+
self.target = target
2630
self.warningCriteria = warningCriteria.converted(to: .seconds).value
2731
self.criticalCriteria = criticalCriteria.converted(to: .seconds).value
32+
self.healthSignalInterval = healthSignalInterval.converted(to: .seconds).value
2833
self.healthSignalCheckInterval = healthSignalCheckInterval.converted(to: .seconds).value
2934
}
3035

@@ -39,11 +44,18 @@ internal final class HealthChecker {
3944
}
4045

4146
private func startImpl() {
42-
self.subscription = Timer.publish(every: self.healthSignalCheckInterval, on: RunLoop.current, in: .common)
47+
let healthSignalCheckTimer = Timer
48+
.publish(every: self.healthSignalCheckInterval, on: RunLoop.current, in: .common)
4349
.autoconnect()
44-
.combineLatest(self.healthSignalSubject.receive(on: RunLoop.current))
50+
let healthSignalStream = Timer
51+
.publish(every: self.healthSignalInterval, on: self.target, in: .common)
52+
.autoconnect()
53+
.prepend(AnyPublisher(Date()).receive(on: self.target))
54+
.receive(on: RunLoop.current)
55+
56+
self.subscription = healthSignalCheckTimer.combineLatest(healthSignalStream)
4557
.compactMap { (now: Date, lastSignal: Date) -> TimeInterval in
46-
now.timeIntervalSince(lastSignal)
58+
return now.timeIntervalSince(lastSignal)
4759
}
4860
.map { (timeDiff: TimeInterval) -> Health in
4961
switch (timeDiff) {
@@ -69,8 +81,4 @@ internal final class HealthChecker {
6981
self.timerThread?.cancel()
7082
self.timerThread = nil
7183
}
72-
73-
func acceptHealthSignal() {
74-
self.healthSignalSubject.send(Date())
75-
}
7684
}

Sources/UIHangDetector/UIHangDetector.swift

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import Foundation
22
import Combine
33

44
public final class UIHangDetector {
5-
private let healthSignalInterval: TimeInterval
6-
private let healthChecker: HealthChecker
5+
private let healthChecker: RunLoopHealthChecker
76
private var timer: Timer?
87

98
public var healthStream: AnyPublisher<Health, Never> {
@@ -18,29 +17,20 @@ public final class UIHangDetector {
1817
healthSignalInterval: Duration = 0.5(.seconds),
1918
healthSignalCheckInterval: Duration = 0.1(.seconds)
2019
) {
21-
self.healthSignalInterval = healthSignalInterval.converted(to: .seconds).value
22-
self.healthChecker = HealthChecker(
20+
self.healthChecker = RunLoopHealthChecker(
21+
target: RunLoop.main,
2322
warningCriteria: warningCriteria,
2423
criticalCriteria: criticalCriteria,
24+
healthSignalInterval: healthSignalInterval,
2525
healthSignalCheckInterval: healthSignalCheckInterval
2626
)
2727
}
2828

2929
public func start() {
3030
self.healthChecker.start()
31-
32-
DispatchQueue.main.async {
33-
self.healthChecker.acceptHealthSignal()
34-
self.timer = Timer.scheduledTimer(withTimeInterval: self.healthSignalInterval, repeats: true) { _ in
35-
self.healthChecker.acceptHealthSignal()
36-
}
37-
}
3831
}
3932

4033
public func stop() {
4134
self.healthChecker.stop()
42-
43-
self.timer?.invalidate()
44-
self.timer = nil
4535
}
4636
}

0 commit comments

Comments
 (0)