diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/MoleculeComparisonProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/MoleculeComparisonProtocol.swift new file mode 100644 index 00000000..f2ac0715 --- /dev/null +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/MoleculeComparisonProtocol.swift @@ -0,0 +1,9 @@ +// +// MoleculeComparisonProtocol.swift +// MVMCoreUI +// +// Created by Kyle Hedden on 4/29/24. +// Copyright © 2024 Verizon Wireless. All rights reserved. +// + +import Foundation diff --git a/MVMCoreUI/Behaviors/PollingBehaviorModel.swift b/MVMCoreUI/Behaviors/PollingBehaviorModel.swift index 7a92941f..5e1058d8 100644 --- a/MVMCoreUI/Behaviors/PollingBehaviorModel.swift +++ b/MVMCoreUI/Behaviors/PollingBehaviorModel.swift @@ -8,6 +8,7 @@ import Foundation import MVMCore +import Combine public class PollingBehaviorModel: PageBehaviorModelProtocol { public class var identifier: String { "pollingBehavior" } @@ -47,15 +48,24 @@ public class PollingBehaviorModel: PageBehaviorModelProtocol { } } -public class PollingBehavior: NSObject, PageVisibilityBehavior { +extension PollingBehaviorModel: CustomDebugStringConvertible { + public var debugDescription: String { + return "\(Self.self) @ \(refreshInterval) firing \(self.refreshAction)" + } +} + +public class PollingBehavior: NSObject, PageVisibilityBehavior, PageMoleculeTransformationBehavior, CoreLogging { + + public static var loggingCategory: String? { return String(describing: Self.self) } var model: PollingBehaviorModel var delegateObject: MVMCoreUIDelegateObject? - var lastRefresh = Date.distantPast + var lastRefresh = Date() // Treat the last refresh as now on init. refreshOnShown will bypass otherwise. var pollTimer: DispatchSourceTimer? + var backgroundEventSubscripiton: AnyCancellable? var remainingTimeToRefresh: TimeInterval { - lastRefresh.timeIntervalSinceNow - model.refreshInterval + model.refreshInterval + lastRefresh.timeIntervalSinceNow // timeIntervalSinceNow in negative since earlier recording (--) } var firstTimeLoad = true @@ -71,6 +81,14 @@ public class PollingBehavior: NSObject, PageVisibilityBehavior { public required init(model: PageBehaviorModelProtocol, delegateObject: MVMCoreUIDelegateObject?) { self.model = model as! PollingBehaviorModel self.delegateObject = delegateObject + Self.debugLog("Initializing for \(model)") + } + + public func onPageNew(rootMolecules: [any MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) { + if let behaviorVC = delegateObject?.moleculeDelegate as? ViewController, MVMCoreUIUtility.getCurrentVisibleController() == behaviorVC { + // If behavior is initialized after the page is shown, we need to start the timer. + resumePollingTimer(withRemainingTime: refreshOnShown ? 0 : remainingTimeToRefresh, refreshAction: model.refreshAction, interval: model.refreshInterval) + } } public func onPageShown(_ delegateObject: MVMCoreUIDelegateObject?) { @@ -79,23 +97,43 @@ public class PollingBehavior: NSObject, PageVisibilityBehavior { public func onPageHidden(_ delegateObject: MVMCoreUIDelegateObject?) { pollTimer?.cancel() + backgroundEventSubscripiton = nil } func resumePollingTimer(withRemainingTime timeRemaining: TimeInterval, refreshAction: ActionModelProtocol, interval: TimeInterval) { let delegateObject = delegateObject pollTimer?.cancel() + let pollingId = UUID().uuidString + debugLog("Scheduling timed event \(pollingId) in \(timeRemaining), interval: \(interval)") pollTimer = DispatchSource.makeTimerSource() pollTimer?.schedule(deadline: .now() + timeRemaining, repeating: interval) - pollTimer?.setEventHandler(qos:.utility) { - Task { + pollTimer?.setEventHandler(qos:.utility) { [weak self] in + guard let self = self else { return } + lastRefresh = Date() + Task { [weak self] in + self?.debugLog("Firing timed event \(pollingId), \(refreshAction)") if let delegateActionHandler = delegateObject?.actionDelegate as? ActionDelegateProtocol { try? await delegateActionHandler.performAction(with: refreshAction, additionalData: nil, delegateObject: delegateObject) } else { try? await MVMCoreActionHandler.shared()?.handleAction(with: refreshAction, additionalData: nil, delegateObject: delegateObject) } + self?.debugLog("Finished timed event \(pollingId)") } } pollTimer?.resume() + setupBackgroundingPause() + } + + private func setupBackgroundingPause() { + backgroundEventSubscripiton = NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification).sink { [weak self] _ in + guard let self = self else { return } + pollTimer?.cancel() + + backgroundEventSubscripiton = NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification).sink { [weak self] _ in + guard let self = self else { return } + resumePollingTimer(withRemainingTime: remainingTimeToRefresh, refreshAction: model.refreshAction, interval: model.refreshInterval) + } + } } deinit {