Search code examples
iosswiftcore-dataswift5combine

How to: Using Combine to react to CoreData changes in the background


I want to achieve the following: Whenever someone triggers a CoreData save (ie. NSManagedObjectContextDidSave notification gets sent), I'd like to perform some background calculation based the changed NSManagedObject. Concrete example: Assume in a notes app, I want to asynchronously calculate the total number of words in all notes.

The problem currently lies with the fact that NSManagedObject context is explicitly bound to thread and you are discouraged from using NSManagedObjects outside this thread.

I have setup two NSManagedObjectContexts in my SceneDelegate:

let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let backgroundContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.newBackgroundContext()

I also have subscribed to the notification via NotificationCenter.default.publisher(for: .NSManagedObjectContextDidSave) and am receiving a save notification twice after I trigger only one managedObjectContext.save(). However, both notifications are sent from the same thread (which is the UIThread) and all NSManagedObjects in the user dictionary have a .managedObjectContext which is the viewContext and not the backgroundContext.

My idea was to filter the notifications based on whether or not the associated NSManagedObjectContext was the background one as I assumed that the notification is also sent on the (private) DispatchQueue but it seems all notifications are sent on the UIThread and the background context is never used.

Any idea on how to solve this? Is this a bug? How can I retrieve notifications based on the backgroundContext with downstream tasks being run on the associated DispatchQueue?


Solution

  • You can pass the object you want to observe to publisher(for:):

    NotificationCenter.default
      .publisher(for: .NSManagedObjectContextDidSave, object: backgroundMoc)
      .sink(receiveValue: { notification in
        // handle changes
      })
    

    That will only listen for notifications related to a background managed object context which means you can do processing on that context's queue safely.