mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
Fix: Memory Leak
This commit is contained in:
parent
8e003b620f
commit
12db48bc25
1 changed files with 94 additions and 16 deletions
|
|
@ -25,10 +25,17 @@ final class ReminderLiveActivityManager: ObservableObject {
|
|||
|
||||
private var nextReminder: ReminderEntry?
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
private var tickerCancellable: AnyCancellable?
|
||||
private var tickerTask: Task<Void, Never>? { didSet { oldValue?.cancel() } }
|
||||
private var evaluationTask: Task<Void, Never>?
|
||||
private var fallbackRefreshTask: Task<Void, Never>?
|
||||
private var refreshTask: Task<Void, Never>?
|
||||
private var pendingRefreshTask: Task<Void, Never>?
|
||||
private var pendingRefreshForce = false
|
||||
private var pendingRefreshToken = UUID()
|
||||
private var lastRefreshDate: Date?
|
||||
private let minimumRefreshInterval: TimeInterval = 10
|
||||
private let refreshDebounceInterval: TimeInterval = 0.3
|
||||
private var refreshTaskToken = UUID()
|
||||
|
||||
private let calendarService: CalendarServiceProviding
|
||||
private let calendarManager = CalendarManager.shared
|
||||
|
|
@ -38,7 +45,7 @@ final class ReminderLiveActivityManager: ObservableObject {
|
|||
private init(calendarService: CalendarServiceProviding = CalendarService()) {
|
||||
self.calendarService = calendarService
|
||||
setupObservers()
|
||||
Task { await self.refreshUpcomingReminder() }
|
||||
scheduleRefresh(force: true)
|
||||
}
|
||||
|
||||
private func setupObservers() {
|
||||
|
|
@ -80,7 +87,7 @@ final class ReminderLiveActivityManager: ObservableObject {
|
|||
calendarManager.$events
|
||||
.sink { [weak self] _ in
|
||||
guard let self else { return }
|
||||
self.scheduleRefresh(force: true)
|
||||
self.scheduleRefresh(force: false)
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
|
|
@ -93,12 +100,14 @@ final class ReminderLiveActivityManager: ObservableObject {
|
|||
}
|
||||
|
||||
private func cancelAllTimers() {
|
||||
tickerCancellable?.cancel()
|
||||
tickerCancellable = nil
|
||||
tickerTask = nil
|
||||
evaluationTask?.cancel()
|
||||
evaluationTask = nil
|
||||
fallbackRefreshTask?.cancel()
|
||||
fallbackRefreshTask = nil
|
||||
pendingRefreshTask?.cancel()
|
||||
pendingRefreshTask = nil
|
||||
pendingRefreshForce = false
|
||||
refreshTask?.cancel()
|
||||
refreshTask = nil
|
||||
}
|
||||
|
|
@ -154,32 +163,92 @@ final class ReminderLiveActivityManager: ObservableObject {
|
|||
fallbackRefreshTask = Task { [weak self] in
|
||||
guard let self else { return }
|
||||
try? await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000))
|
||||
await self.refreshUpcomingReminder()
|
||||
await self.scheduleRefresh(force: true)
|
||||
}
|
||||
}
|
||||
|
||||
private func scheduleRefresh(force: Bool) {
|
||||
pendingRefreshForce = pendingRefreshForce || force
|
||||
pendingRefreshToken = UUID()
|
||||
let token = pendingRefreshToken
|
||||
|
||||
refreshTask?.cancel()
|
||||
refreshTask = Task { [weak self] in
|
||||
pendingRefreshTask?.cancel()
|
||||
pendingRefreshTask = Task { [weak self] in
|
||||
guard let self else { return }
|
||||
await self.refreshUpcomingReminder(force: force)
|
||||
do {
|
||||
try await Task.sleep(nanoseconds: UInt64(self.refreshDebounceInterval * 1_000_000_000))
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
await self.executeScheduledRefresh(token: token)
|
||||
}
|
||||
}
|
||||
|
||||
private func startTickerIfNeeded() {
|
||||
guard tickerCancellable == nil else { return }
|
||||
tickerCancellable = Timer.publish(every: 1, tolerance: 0.2, on: .main, in: .common)
|
||||
.autoconnect()
|
||||
.sink { [weak self] date in
|
||||
guard let self else { return }
|
||||
self.currentDate = date
|
||||
Task { await self.evaluateCurrentState(at: date) }
|
||||
private func executeScheduledRefresh(token: UUID) async {
|
||||
guard pendingRefreshToken == token else { return }
|
||||
pendingRefreshTask = nil
|
||||
|
||||
if Task.isCancelled {
|
||||
return
|
||||
}
|
||||
|
||||
let now = Date()
|
||||
let force = pendingRefreshForce
|
||||
if !force, let last = lastRefreshDate {
|
||||
let elapsed = now.timeIntervalSince(last)
|
||||
if elapsed < minimumRefreshInterval {
|
||||
let remaining = max(minimumRefreshInterval - elapsed, refreshDebounceInterval)
|
||||
pendingRefreshToken = UUID()
|
||||
let nextToken = pendingRefreshToken
|
||||
pendingRefreshTask = Task { [weak self] in
|
||||
guard let self else { return }
|
||||
do {
|
||||
try await Task.sleep(nanoseconds: UInt64(remaining * 1_000_000_000))
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
await self.executeScheduledRefresh(token: nextToken)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
pendingRefreshForce = false
|
||||
lastRefreshDate = now
|
||||
|
||||
refreshTask?.cancel()
|
||||
let taskToken = UUID()
|
||||
refreshTaskToken = taskToken
|
||||
let task = Task { [weak self] in
|
||||
guard let self else { return }
|
||||
await self.refreshUpcomingReminder(force: force)
|
||||
}
|
||||
refreshTask = task
|
||||
defer {
|
||||
if refreshTaskToken == taskToken {
|
||||
refreshTask = nil
|
||||
}
|
||||
}
|
||||
_ = try? await task.value
|
||||
}
|
||||
|
||||
private func startTickerIfNeeded() {
|
||||
guard tickerTask == nil else { return }
|
||||
tickerTask = Task { [weak self] in
|
||||
while let self, !Task.isCancelled {
|
||||
await self.handleTick()
|
||||
do {
|
||||
try await Task.sleep(for: .seconds(1))
|
||||
} catch {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func stopTicker() {
|
||||
tickerCancellable?.cancel()
|
||||
tickerCancellable = nil
|
||||
tickerTask = nil
|
||||
}
|
||||
|
||||
private func handleEntrySelection(_ entry: ReminderEntry?, referenceDate: Date) {
|
||||
|
|
@ -277,6 +346,15 @@ final class ReminderLiveActivityManager: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func handleTick() async {
|
||||
let now = Date()
|
||||
if abs(currentDate.timeIntervalSince(now)) >= 0.5 {
|
||||
currentDate = now
|
||||
}
|
||||
await evaluateCurrentState(at: now)
|
||||
}
|
||||
|
||||
private func glyphName(for event: EventModel) -> String {
|
||||
switch event.type {
|
||||
case .birthday:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue