Skip to content

Commit

Permalink
Refactor UIHangeDetector
Browse files Browse the repository at this point in the history
  • Loading branch information
wplong11 committed Jul 18, 2022
1 parent 8a33bc1 commit 5e0231b
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 25 deletions.
4 changes: 2 additions & 2 deletions Demo/UIHangDetectorDemo/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?

private let hangDetector = UIHangDetector(
warningCriteria: 500(.milliseconds),
criticalCriteria: 1(.seconds),
warningCriteria: 600(.milliseconds),
criticalCriteria: 1000(.milliseconds),
healthSignalInterval: 500(.milliseconds),
healthSignalCheckInterval: 100(.milliseconds)
)
Expand Down
10 changes: 10 additions & 0 deletions Sources/UIHangDetector/AnyPublisher+init.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Foundation
import Combine

internal extension AnyPublisher where Failure == Never {
init(_ output: Output) {
self = Optional.Publisher(output)
.compactMap { $0 }
.eraseToAnyPublisher()
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import Foundation
import Combine

internal final class HealthChecker {
internal final class RunLoopHealthChecker {
private let healthSubject = PassthroughSubject<Health, Never>()
private let healthSignalSubject = CurrentValueSubject<Date, Never>(Date.distantPast)

private var timerThread: Thread?
private var subscription: AnyCancellable?

private let target: RunLoop
private let warningCriteria: TimeInterval
private let criticalCriteria: TimeInterval
private let healthSignalInterval: TimeInterval
private let healthSignalCheckInterval: TimeInterval

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

init(
target: RunLoop,
warningCriteria: Duration,
criticalCriteria: Duration,
healthSignalInterval: Duration,
healthSignalCheckInterval: Duration
) {
self.target = target
self.warningCriteria = warningCriteria.converted(to: .seconds).value
self.criticalCriteria = criticalCriteria.converted(to: .seconds).value
self.healthSignalInterval = healthSignalInterval.converted(to: .seconds).value
self.healthSignalCheckInterval = healthSignalCheckInterval.converted(to: .seconds).value
}

Expand All @@ -39,11 +44,18 @@ internal final class HealthChecker {
}

private func startImpl() {
self.subscription = Timer.publish(every: self.healthSignalCheckInterval, on: RunLoop.current, in: .common)
let healthSignalCheckTimer = Timer
.publish(every: self.healthSignalCheckInterval, on: RunLoop.current, in: .common)
.autoconnect()
.combineLatest(self.healthSignalSubject.receive(on: RunLoop.current))
let healthSignalStream = Timer
.publish(every: self.healthSignalInterval, on: self.target, in: .common)
.autoconnect()
.prepend(AnyPublisher(Date()).receive(on: self.target))
.receive(on: RunLoop.current)

self.subscription = healthSignalCheckTimer.combineLatest(healthSignalStream)
.compactMap { (now: Date, lastSignal: Date) -> TimeInterval in
now.timeIntervalSince(lastSignal)
return now.timeIntervalSince(lastSignal)
}
.map { (timeDiff: TimeInterval) -> Health in
switch (timeDiff) {
Expand All @@ -69,8 +81,4 @@ internal final class HealthChecker {
self.timerThread?.cancel()
self.timerThread = nil
}

func acceptHealthSignal() {
self.healthSignalSubject.send(Date())
}
}
18 changes: 4 additions & 14 deletions Sources/UIHangDetector/UIHangDetector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import Foundation
import Combine

public final class UIHangDetector {
private let healthSignalInterval: TimeInterval
private let healthChecker: HealthChecker
private let healthChecker: RunLoopHealthChecker
private var timer: Timer?

public var healthStream: AnyPublisher<Health, Never> {
Expand All @@ -18,29 +17,20 @@ public final class UIHangDetector {
healthSignalInterval: Duration = 0.5(.seconds),
healthSignalCheckInterval: Duration = 0.1(.seconds)
) {
self.healthSignalInterval = healthSignalInterval.converted(to: .seconds).value
self.healthChecker = HealthChecker(
self.healthChecker = RunLoopHealthChecker(
target: RunLoop.main,
warningCriteria: warningCriteria,
criticalCriteria: criticalCriteria,
healthSignalInterval: healthSignalInterval,
healthSignalCheckInterval: healthSignalCheckInterval
)
}

public func start() {
self.healthChecker.start()

DispatchQueue.main.async {
self.healthChecker.acceptHealthSignal()
self.timer = Timer.scheduledTimer(withTimeInterval: self.healthSignalInterval, repeats: true) { _ in
self.healthChecker.acceptHealthSignal()
}
}
}

public func stop() {
self.healthChecker.stop()

self.timer?.invalidate()
self.timer = nil
}
}

0 comments on commit 5e0231b

Please sign in to comment.