Simultaneous and reliable use of master data

I am creating my first iOS application, which theoretically should be quite simple, but I am having difficulty making it bulletproof enough for me to confidently present it in the App Store.

In short, there is a tabular view on the main screen, after selecting a row that goes into another tabular view that displays information related to the selected row in master part mode. The underlying data is retrieved as JSON data from the web service once a day, and then cached in the master data store. Data preceding this day is deleted to stop the SQLite database file indefinitely. All data storage operations are performed using Core Data with the NSFetchedResultsController underlying the presentation of the detail table.

The problem I see is that if you switch between the main and detailed screens several times, while the fresh data is extracted, analyzed and saved, the application completely freezes or crashes. There seems to be some kind of race condition, possibly due to importing Core Data data in the background, while the main thread is trying to fetch, but I'm thinking. I am having trouble writing any meaningful alarm data, usually SIGSEGV in depth to the master data stack.

The following table shows the actual order of events that occur when the detail table view controller loads:

  Main Thread Background Thread
 viewDidLoad

                                      Get JSON data (using AFNetworking)

 Create child NSManagedObjectContext (MOC)

                                      Parse json data
                                      Insert managed objects in child MOC
                                      Save child MOC
                                      Post import completion notification

 Receive import completion notification
 Save parent MOC
 Perform fetch and reload table view

                                      Delete old managed objects in child MOC
                                      Save child MOC
                                      Post deletion completion notification

 Receive deletion completion notification
 Save parent MOC

As soon as the AFNetworking termination block starts when JSON data arrives, NSManagedObjectContext is created and embedded in the importer object, which parses the JSON data and stores the objects in the master data store. The importer performs using the new performBlock method introduced in iOS 5:

 NSManagedObjectContext *child = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; [child setParentContext:self.managedObjectContext]; [child performBlock:^{ // Create importer instance, passing it the child MOC... }]; 

The importer object monitors its own MOC NSManagedObjectContextDidSaveNotification , and then sends its own notification, which is observed by the detail table view controller. When this notification is published, the table view controller saves its own (parent) MOC.

I use the same basic template with the “deleter” object to delete old data after importing new data for the day. This happens asynchronously after new data has been obtained using the selected result controller, and the detail table view has been reloaded.

One thing I don’t do is to observe any merge notifications or block any of the managed object contexts or the permanent repository coordinator. Is this something I have to do? I'm a little unsure how to archive this correctly, so I would appreciate any advice.

+7
source share
4 answers

Pre-iOS 5, we usually had two NSManagedObjectContexts : one for the main thread, one for the background thread. A background thread can load or delete data and then save. The resulting NSManagedObjectContextDidSaveNotification then passed (as you do) to the main thread. We called mergeChangesFromManagedObjectContextDidSaveNotification: to bring them into the context of the main thread. It worked well for us.

One important aspect of this is that save: is blocked in the background thread until mergeChangesFromManagedObjectContextDidSaveNotification: is finished executing the main thread (because we call mergeChanges ... from the listener to this notification). This ensures that the main object associated with the object sees these changes. I do not know if you need to do this if you have a parent-child relationship, but you did it in the old model to avoid various troubles.

I am not sure the advantage of having a relationship between parents and children between these two contexts. You can see from your description that the final save to disk occurs in the main stream, which is probably not ideal for improving performance. (Especially if you can delete a large amount of data, the main cost of deletion in our applications always occurred during the final save to disk.)

What code do you use when controllers appear / disappear, which can cause problems with master data? What stack traces do you see fail?

+3
source

Just an architectural idea:

With your declared data update template (once a day, the complete data cycle is deleted and added), in fact, I would be interested in creating a new persistent store every day (i.e. named for the calendar date), and then in completing the notification , set the table to the new fetchedresultscontroller associated with the new repository (and probably the new MOC), and update using this. Then the application can (in another place, possibly also caused by this notification) completely destroy the "old" data warehouse. This method separates the update processing from the data store that the application is currently using, and the “switch” to new data can be considered more atomic, because this change just starts pointing to new data, rather than hoping that you do not capture the storage in inconsistent state while new data is being written (but not yet completed).

Obviously, I left some details, but I am inclined to think that many data that changes during use should be re-modified again to reduce the likelihood that you encounter.

We will be pleased to discuss further ...

+2
source

The main problem that I encountered with multi-threaded basic data is inadvertently accessing a managed object in a thread / queue other than the one in which it was created.

I found a good debugging tool that NSAsserts adds to verify that only managed objects created in your main managed object context are used only there, and those created in the background are not used in the main context.

This will result in a subclass of NSManagedObjectContext and NSManagedObject:

  • Add iVar to the MOC subclass and assign it the queue to which it was created.
  • A subclass of your MO must verify that the current queue matches the MOC queue property.

These are just a few lines of code, but in the long run, it can stop you from making mistakes that are hard to track otherwise.

+2
source

NSFetchedResultsController has been proven to be a bit sensitive to massive deletions, so I'll start digging first.

My initial question is: how do re-fetching and reloading the tableview relate to the start of the delete operation. Is there a chance that the deletion block will retain the child MOC while the NSFetchedResultsController is still retrieved or not?

Is it possible that when switching from the detailed view to the main one, and then on the reverse to the detailed view, several parallel background tasks will be performed? Or are you extracting all the data from the web service at the same time, not only what relates to a particular row?

One alternative to make this more reliable is to use a template similar to what UIManagedDocument uses:

Instead of using the parent MOC as the main concurrency stream, the UIManagedDocument type actually creates the main MOC as a private queue and makes the child MOC available to you in the main stream. The advantage here is that all I / O operations continue in the background and are stored in the parent MOC, do not interfere with the MOC child at all until the child MOC explicitly finds out about them. This is because saving captures changes from child to parent, and not vice versa.

So, if you deleted in the parent queue, which is private, it will not appear in the NSFetchedResultsController scope at NSFetchedResultsController . And since this is old data, this is actually the preferred way.

The alternative that I propose is to use three contexts:

Primary MOC ( NSPrivateQueueConcurrencyType )

  • Responsible for the permanent storage and deletion of old data.

Child MOC A ( NSMainQueueConcurrencyType )

  • Responsible for any UI related issues and NSFetchedResultsController

Child MOC B ( NSPrivateQueueConcurrencyType , Child Child MOC A)

  • Responsible for inserting new data and transferring it to Child MOC A when done.
+2
source

All Articles