2
votes

I've already done research in lots of articles and discussions about how to use NSManagedObjectContext, but still could't find a satisfying architecture for my project.

In my app, there are three sources where the data might be modified from, ordered by their priorities when there is conflict simultaneously (ex. the cloud has the least priority):

  1. User interface,
  2. BLE message,
  3. HTTP response from the cloud

Since I'm still not an expert on iOS development, I tried to avoid using multiple context for each source. However, after weeks of trial and error, I am reluctant but start to think if I really need to go for multi-context approach.

At the very beginning, I tried to use context.perform { } on the main context to do all the data change operations (add/update/delete, except fetch). I kept fetch to be a sync function because I want the data fetch to be instant so that can responsive with the UI. However, under this approach, I occasionally receive "Collection <__NSCFSet: 0x000000000> was mutated while being enumerated" exception (which I suppose might happen in the forEach or map function for batch data processing). I also found that this approach still block the UI when there are bunch of records to update in the background queue.

Therefore, I created a background context and use parent-child model to manipulate data. Basically the main context(parent) is only responsible for fetching data, while the background context(child) manipulates all the data change (add/update/delete) via backgroundContext.perform { }. This approach solves the UI block problem, however the collection mutated error still happen occasionally, and another issue occurs under this structure: for instance, the app would crash when I add a data record in ViewController A, and move to View Controller B, which fetch the same data immediately even if the background context haven't finished adding the data record.

Therefore I would like to ask some suggestions for the Core Data usage on my project. Is there something I did wrong under my parent-child data context model? Or, should I inevitably go for a real multi-context model without parent-child? How to do it?

My main context(parent) and background context(child) are initiated like this:

lazy var _context: NSManagedObjectContext = {
        return (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
    }()

lazy var _backgroundContext: NSManagedObjectContext = {
    let ctx = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.privateQueueConcurrencyType)
    ctx.parent = self._context
    return ctx
}()

And the merge function is as follow:

@objc func contextDidSaveContext(notification: NSNotification) {
    let sender = notification.object as! NSManagedObjectContext

    if sender === self._context {
        NSLog("******** Saved main Context in this thread")
        self._backgroundContext.perform {
            self._backgroundContext.mergeChanges(fromContextDidSave: notification as Notification)
        }
    } else if sender === self._backgroundContext {
        NSLog("******** Saved background Context in this thread")
        self._context.perform {
            self._context.mergeChanges(fromContextDidSave: notification as Notification)
        }
    }
    else {
        NSLog("******** Saved Context in other thread")
        self._context.perform {
            self._context.mergeChanges(fromContextDidSave: notification as Notification)
        }
        self._backgroundContext.perform {
            self._backgroundContext.mergeChanges(fromContextDidSave: notification as Notification)
        }
    }
}

Any advice of the core data structure would be appreciated.

1

1 Answers

0
votes

Have you explored to use NSFetchedResultsController to fetch the data? NSFetchedResultsController provides a thread safe way to observe for changes in the data due to Create, update or delete operations in CoreData. Use NSFetchedResultsController's delegate methods to update the UI. Ideally, controllerDidChangeContent delegate would give you an indication to update the UI. Documentation for reference