diff options
Diffstat (limited to 'DomainDig/SweepActivityController.swift')
| -rw-r--r-- | DomainDig/SweepActivityController.swift | 39 |
1 files changed, 27 insertions, 12 deletions
diff --git a/DomainDig/SweepActivityController.swift b/DomainDig/SweepActivityController.swift index a00bb2f..e6d179e 100644 --- a/DomainDig/SweepActivityController.swift +++ b/DomainDig/SweepActivityController.swift @@ -2,11 +2,18 @@ import ActivityKit import Foundation /// Starts, updates, and ends the sweep Live Activity around a batch run. +/// +/// Holds the activity's `id` (a Sendable `String`) rather than the +/// `Activity` object itself. `Activity` is not Sendable, and sending the +/// stored reference into the fire-and-forget update task while `self` still +/// held it was a Swift 6 region-isolation violation (issue #27). Each task +/// re-resolves the activity via `Activity.activities`, ActivityKit's +/// sanctioned lookup, so nothing non-Sendable crosses an isolation boundary. @MainActor final class SweepActivityController { static let shared = SweepActivityController() - private var activity: Activity<SweepActivityAttributes>? + private var activityID: String? private init() { /* Singleton; use the shared instance. */ } @@ -23,14 +30,15 @@ final class SweepActivityController { changed: 0, warnings: 0 ) - activity = try? Activity.request( + let activity = try? Activity.request( attributes: SweepActivityAttributes(title: title, startedAt: Date()), content: ActivityContent(state: state, staleDate: nil) ) + activityID = activity?.id } func update(completed: Int, total: Int, currentDomain: String?) { - guard let activity else { return } + guard let activityID else { return } let state = SweepActivityAttributes.ContentState( completed: completed, total: total, @@ -39,25 +47,32 @@ final class SweepActivityController { warnings: 0 ) Task { + guard let activity = Self.activity(withID: activityID) else { return } await activity.update(ActivityContent(state: state, staleDate: nil)) } } func end(changed: Int, warnings: Int, immediately: Bool = false) { - guard let activity else { return } - self.activity = nil - let state = SweepActivityAttributes.ContentState( - completed: activity.content.state.total, - total: activity.content.state.total, - currentDomain: nil, - changed: changed, - warnings: warnings - ) + guard let activityID else { return } + self.activityID = nil Task { + guard let activity = Self.activity(withID: activityID) else { return } + let total = activity.content.state.total + let state = SweepActivityAttributes.ContentState( + completed: total, + total: total, + currentDomain: nil, + changed: changed, + warnings: warnings + ) await activity.end( ActivityContent(state: state, staleDate: nil), dismissalPolicy: immediately ? .immediate : .after(Date().addingTimeInterval(60)) ) } } + + private nonisolated static func activity(withID id: String) -> Activity<SweepActivityAttributes>? { + Activity<SweepActivityAttributes>.activities.first { $0.id == id } + } } |
