📜 ⬆️ ⬇️

Multiple Core Data

As you know, Core Data is a powerful Apple framework for managing an object graph. On Habré, there are quite a few articles about Core Data; nevertheless, multithreading is lit rather poorly, and, as it seems to me, almost everyone asked how to implement it correctly.

General provisions


In short, the Core Data stack consists of several main parts.



1) NSPersistentStore, which can be a binary file, XML, SQLite file.
2) NSManagedObjectModel, which is a compiled binary version of the data model.
3) NSPersistentStoreCoordinator, is engaged in loading data from NSPersistentStore and NSManagedObjectModel, saving and caching.
4) NSManagedObjectContext, loading data from the NSPersistentStore into memory, operations with instances.
5) NSManagedObject is a data model object.
')
The main, in my opinion, unpleasant feature of this whole miracle is that NSManagedObjectContext is not thread-safe.

Stack initialization


With large database sizes, during migrations, the initialization of the stack on the main thread can take more than 30 seconds. This will cause the system to simply kill the application. There is a way to initialize the stack in another thread.

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //  NSManagedObjectModel NSURL *modelURL = [[NSBundle mainBundle] URLForResource:kModelFileName withExtension:@"momd"]; NSManagedObjectModel *mom = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; //  NSPersistentStoreCoordinator NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom]; //   NSPersistentStoreCoordinator ,          NSURL *doURL = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; NSURL *storeURL = [doURL URLByAppendingPathComponent:@"CoreData.sqlite"]; NSError *error = nil; NSPersistentStore *store = [psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]; //   }); 


So, our application was launched, the user did not receive lags UI, everyone is happy. We follow on.

Creating main contexts


As I wrote above, NSManagedObjectContext is not thread-safe. Therefore, it is appropriate to keep the main application context on the main thread. But in this case it will slow down the UI while maintaining this context, what to do? But that, in iOS 6, the NSManagedObjectContext types appeared.

1) NSMainQueueConcurrencyType - available exclusively from the main thread.
2) NSPrivateQueueConcurrencyType - runs on a background thread.
3) NSConfinementConcurrencyType - runs on the stream on which it was created.

And also the opportunity to create child contexts. The child context while saving pulls all its changes to the parent. Accordingly, it is now possible to equip your manager to work with CoreData as follows.

 //           . // _daddyManagedObjectContext        ,  . _daddyManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; [_daddyManagedObjectContext setPersistentStoreCoordinator:psc]; //      main-thread context,     dispatch_async(dispatch_get_main_queue(), ^{ _defaultManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; //     ,       [_defaultManagedObjectContext setParentContext:_daddyManagedObjectContext]; }); 


At this point, the initialization of our Core Data manager is over, now we have a father-context hidden from prying eyes, and a daughter on the main thread.

Creating child contexts


As you can easily guess, when working on background threads, we can create our contexts simply by adding our child context created above as an ancestor.

 - (NSManagedObjectContext *)getContextForBGTask { NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; [context setParentContext:_defaultManagedObjectContext]; return context; } 


Such a context while saving will always save changes to its parent. So we will always have the most up-to-date information in _defaultManagedObjectContext (which puts changes into the real parent).

Saving Contexts


The only thing left is preservation. Contexts that live on background threads can only be accessed through performBlock: and performBlockAndWait :. Therefore, saving the background of the stream will look like this.

 - (void)saveContextForBGTask:(NSManagedObjectContext *)bgTaskContext { if (bgTaskContext.hasChanges) { [bgTaskContext performBlockAndWait:^{ NSError *error = nil; [backgroundTaskContext save:&error]; }]; // Save default context } } 


After saving the child context, you must save the parent.

 - (void)saveDefaultContext:(BOOL)wait { if (_defaultManagedObjectContext.hasChanges) { [_defaultManagedObjectContext performBlockAndWait:^{ NSError *error = nil; [_defaultManagedObjectContext save:&error]; }]; } //    _defaultManagedObjectContext    ,   _daddyManagedObjectContext void (^saveDaddyContext) (void) = ^{ NSError *error = nil; [_daddyManagedObjectContext save:&error]; }; if ([_daddyManagedObjectContext hasChanges]) { if (wait) [_daddyManagedObjectContext performBlockAndWait:saveDaddyContext]; else [_daddyManagedObjectContext performBlock:saveDaddyContext]; } } 


Conclusion


For several years, I often hear from developers that Core Data has a large number of drawbacks, so the choice is made in favor of, for example, FMDB , the main argument is multithreading, or rather, allegedly, its absence in Core Data. The purpose of the article is precisely to dispel this myth.
A lot of frameworks are written to work with Core Data, the main one, in my opinion, is MagicalRecord . Includes a huge amount of functionality. It is worth noting that the inside works approximately according to the method described above. Any framework needs to be correctly applied, and therefore it should be understood how it works.

That's all. Thanks for attention.

Source: https://habr.com/ru/post/218457/


All Articles