iosswiftcloudkit

Capture of 'objectIDNotifications' with non-Sendable type '[Notification]' in a '@Sendable' closure


I'm using Swift5 and SwiftUI making an app that based on CloudKit. I've made the merge data from iCloud function and it looks work well. But I always get a warning like "Capture of 'objectIDNotifications' with non-Sendable type '[Notification]' in a '@Sendable' closure". My code is basically refer to Apple's demo

Warning message:

AppState.swift:170:13 Capture of 'objectIDNotifications' with non-Sendable type '[Notification]' in a '@Sendable' closure

Generic struct 'Array' does not conform to the 'Sendable' protocol (Swift.Array)

My code:

extension AppState {
    
    private func mergeRemoteChanges(_ notification: Notification) {
        guard let transactions = notification.userInfo?[UserInfoKey.transactions] as? [NSPersistentHistoryTransaction] else {
            return
        }
        
        let objectIDNotifications = transactions.compactMap { $0.objectIDNotification() }
        
        let viewContext = PersistenceController.shared.persistentContainer.viewContext

        viewContext.perform {
            objectIDNotifications.forEach { changes in // warning here
                viewContext.mergeChanges(fromContextDidSave: changes)
                AppLog.data.trace("Merged remote changes")
            }
        }
    }
    
}

Apple's code:

  func photoTransactions(from notification: Notification) -> [NSPersistentHistoryTransaction] {
        var results = [NSPersistentHistoryTransaction]()
        if let transactions = notification.userInfo?[UserInfoKey.transactions] as? [NSPersistentHistoryTransaction] {
            let photoEntityName = Photo.entity().name
            for transaction in transactions where transaction.changes != nil {
                for change in transaction.changes! where change.changedObjectID.entity.name == photoEntityName {
                    results.append(transaction)
                    break // Jump to the next transaction.
                }
            }
        }
        return results
    }
    
    func mergeTransactions(_ transactions: [NSPersistentHistoryTransaction], to context: NSManagedObjectContext) {
        context.perform {
            for transaction in transactions {
                context.mergeChanges(fromContextDidSave: transaction.objectIDNotification())
            }
        }
    }

Solution

  • You need to add @preconcurrency and replace mapping inside perform action

    @preconcurrency import CoreData
    
    extension AppState {
    
        private func mergeRemoteChanges(_ notification: Notification) {
            guard let transactions = notification.userInfo?[UserInfoKey.transactions] as? [NSPersistentHistoryTransaction] else {
                return
            }
    
            let viewContext = PersistenceController.shared.persistentContainer.viewContext
    
            viewContext.perform {
                let objectIDNotifications = transactions.compactMap { $0.objectIDNotification() }
                objectIDNotifications.forEach { changes in
                    viewContext.mergeChanges(fromContextDidSave: changes)
                    AppLog.data.trace("Merged remote changes")
                }
            }
        }
    }
    

    Core Data is still an Objective-C–based framework and isn’t fully annotated for Swift’s strict concurrency checks yet. This prevents Swift 6 from requiring Sendable for all captured Core Data types inside @Sendable closures.

    It will work as a temporary migration step until Core Data is updated with proper concurrency annotations.