fix: crash on launch from BGTask handler @MainActor isolation violation
BGTaskScheduler.register(using: nil) invokes the handler on a background queue, but the closure captured @MainActor-isolated self. Swift 6 runtime enforces this with dispatch_assert_queue which crashed on Thread 4. Fix: pass DispatchQueue.main as the handler queue so the callback runs on the main queue, satisfying @MainActor isolation. Also fix expiration handlers to capture a local Logger copy instead of accessing self. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -60,9 +60,12 @@ final class BackgroundSyncManager {
|
|||||||
/// Must be called early in app lifecycle, before `applicationDidFinishLaunching` returns.
|
/// Must be called early in app lifecycle, before `applicationDidFinishLaunching` returns.
|
||||||
func registerTasks() {
|
func registerTasks() {
|
||||||
// Register refresh task (for periodic CloudKit sync)
|
// Register refresh task (for periodic CloudKit sync)
|
||||||
|
// using: DispatchQueue.main ensures the handler runs on the main queue,
|
||||||
|
// satisfying @MainActor isolation (using: nil invokes on a background queue,
|
||||||
|
// which crashes with dispatch_assert_queue_fail in Swift 6)
|
||||||
BGTaskScheduler.shared.register(
|
BGTaskScheduler.shared.register(
|
||||||
forTaskWithIdentifier: Self.refreshTaskIdentifier,
|
forTaskWithIdentifier: Self.refreshTaskIdentifier,
|
||||||
using: nil
|
using: DispatchQueue.main
|
||||||
) { [weak self] task in
|
) { [weak self] task in
|
||||||
guard let task = task as? BGAppRefreshTask else { return }
|
guard let task = task as? BGAppRefreshTask else { return }
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
@@ -73,7 +76,7 @@ final class BackgroundSyncManager {
|
|||||||
// Register processing task (for overnight heavy sync/cleanup)
|
// Register processing task (for overnight heavy sync/cleanup)
|
||||||
BGTaskScheduler.shared.register(
|
BGTaskScheduler.shared.register(
|
||||||
forTaskWithIdentifier: Self.processingTaskIdentifier,
|
forTaskWithIdentifier: Self.processingTaskIdentifier,
|
||||||
using: nil
|
using: DispatchQueue.main
|
||||||
) { [weak self] task in
|
) { [weak self] task in
|
||||||
guard let task = task as? BGProcessingTask else { return }
|
guard let task = task as? BGProcessingTask else { return }
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
@@ -154,8 +157,10 @@ final class BackgroundSyncManager {
|
|||||||
currentCancellationToken = cancellationToken
|
currentCancellationToken = cancellationToken
|
||||||
|
|
||||||
// Set up expiration handler - cancel sync gracefully
|
// Set up expiration handler - cancel sync gracefully
|
||||||
task.expirationHandler = { [weak self] in
|
// Note: expirationHandler runs on an arbitrary queue, so avoid accessing @MainActor self
|
||||||
self?.logger.warning("Background refresh task expiring - cancelling sync")
|
let expirationLogger = logger
|
||||||
|
task.expirationHandler = {
|
||||||
|
expirationLogger.warning("Background refresh task expiring - cancelling sync")
|
||||||
cancellationToken.cancel()
|
cancellationToken.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,8 +201,9 @@ final class BackgroundSyncManager {
|
|||||||
currentCancellationToken = cancellationToken
|
currentCancellationToken = cancellationToken
|
||||||
|
|
||||||
// Set up expiration handler - cancel sync gracefully
|
// Set up expiration handler - cancel sync gracefully
|
||||||
task.expirationHandler = { [weak self] in
|
let processingLogger = logger
|
||||||
self?.logger.warning("Background processing task expiring - cancelling sync")
|
task.expirationHandler = {
|
||||||
|
processingLogger.warning("Background processing task expiring - cancelling sync")
|
||||||
cancellationToken.cancel()
|
cancellationToken.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user