mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-28 23:54:32 +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 nextReminder: ReminderEntry?
|
||||||
private var cancellables = Set<AnyCancellable>()
|
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 evaluationTask: Task<Void, Never>?
|
||||||
private var fallbackRefreshTask: Task<Void, Never>?
|
private var fallbackRefreshTask: Task<Void, Never>?
|
||||||
private var refreshTask: 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 calendarService: CalendarServiceProviding
|
||||||
private let calendarManager = CalendarManager.shared
|
private let calendarManager = CalendarManager.shared
|
||||||
|
|
@ -38,7 +45,7 @@ final class ReminderLiveActivityManager: ObservableObject {
|
||||||
private init(calendarService: CalendarServiceProviding = CalendarService()) {
|
private init(calendarService: CalendarServiceProviding = CalendarService()) {
|
||||||
self.calendarService = calendarService
|
self.calendarService = calendarService
|
||||||
setupObservers()
|
setupObservers()
|
||||||
Task { await self.refreshUpcomingReminder() }
|
scheduleRefresh(force: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupObservers() {
|
private func setupObservers() {
|
||||||
|
|
@ -80,7 +87,7 @@ final class ReminderLiveActivityManager: ObservableObject {
|
||||||
calendarManager.$events
|
calendarManager.$events
|
||||||
.sink { [weak self] _ in
|
.sink { [weak self] _ in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
self.scheduleRefresh(force: true)
|
self.scheduleRefresh(force: false)
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
|
@ -93,12 +100,14 @@ final class ReminderLiveActivityManager: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func cancelAllTimers() {
|
private func cancelAllTimers() {
|
||||||
tickerCancellable?.cancel()
|
tickerTask = nil
|
||||||
tickerCancellable = nil
|
|
||||||
evaluationTask?.cancel()
|
evaluationTask?.cancel()
|
||||||
evaluationTask = nil
|
evaluationTask = nil
|
||||||
fallbackRefreshTask?.cancel()
|
fallbackRefreshTask?.cancel()
|
||||||
fallbackRefreshTask = nil
|
fallbackRefreshTask = nil
|
||||||
|
pendingRefreshTask?.cancel()
|
||||||
|
pendingRefreshTask = nil
|
||||||
|
pendingRefreshForce = false
|
||||||
refreshTask?.cancel()
|
refreshTask?.cancel()
|
||||||
refreshTask = nil
|
refreshTask = nil
|
||||||
}
|
}
|
||||||
|
|
@ -154,32 +163,92 @@ final class ReminderLiveActivityManager: ObservableObject {
|
||||||
fallbackRefreshTask = Task { [weak self] in
|
fallbackRefreshTask = Task { [weak self] in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
try? await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000))
|
try? await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000))
|
||||||
await self.refreshUpcomingReminder()
|
await self.scheduleRefresh(force: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func scheduleRefresh(force: Bool) {
|
private func scheduleRefresh(force: Bool) {
|
||||||
|
pendingRefreshForce = pendingRefreshForce || force
|
||||||
|
pendingRefreshToken = UUID()
|
||||||
|
let token = pendingRefreshToken
|
||||||
|
|
||||||
refreshTask?.cancel()
|
refreshTask?.cancel()
|
||||||
refreshTask = Task { [weak self] in
|
pendingRefreshTask?.cancel()
|
||||||
|
pendingRefreshTask = Task { [weak self] in
|
||||||
guard let self else { return }
|
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() {
|
private func executeScheduledRefresh(token: UUID) async {
|
||||||
guard tickerCancellable == nil else { return }
|
guard pendingRefreshToken == token else { return }
|
||||||
tickerCancellable = Timer.publish(every: 1, tolerance: 0.2, on: .main, in: .common)
|
pendingRefreshTask = nil
|
||||||
.autoconnect()
|
|
||||||
.sink { [weak self] date in
|
if Task.isCancelled {
|
||||||
guard let self else { return }
|
return
|
||||||
self.currentDate = date
|
}
|
||||||
Task { await self.evaluateCurrentState(at: date) }
|
|
||||||
|
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() {
|
private func stopTicker() {
|
||||||
tickerCancellable?.cancel()
|
tickerTask = nil
|
||||||
tickerCancellable = nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleEntrySelection(_ entry: ReminderEntry?, referenceDate: Date) {
|
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 {
|
private func glyphName(for event: EventModel) -> String {
|
||||||
switch event.type {
|
switch event.type {
|
||||||
case .birthday:
|
case .birthday:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue