My code looks like something like this:
dispatch_async(background_save_queue, ^{
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
[context setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
[context setPersistentStoreCoordinator:coordinator];
[[NSNotificationCenter defaultCenter] addObserver:mainContext selector:#selector(mergeChangesFromContextDidSaveNotification:) name:NSManagedObjectContextDidSaveNotification object:context];
//code to create objects with core data in context ("context" variable)
[context save:nil];
[context release];
}
And the similar code without multithread (with one context only) works much better.
Is my code wrong and are there are other examples of filling core data objects with multithreading?
Finally I checked that another thread is not slower than the main one. The problem is there are a lot of records in a data source and I need to perform a check for each record if this record already written in database. So the speed of my database creation process depends greatly on a count of the records and a database structure.
Related
I am currently developing an application that uses Core Data to store data. The application synchronizes its content with a web server by downloading and parsing a huge XML file (about 40000 entries). The application allows the user to search data and modify it (CRUD). The fetch operations are too heavy, that is why i decided to use the following pattern :
"One managed object context for the main thread (NSMainQueueConcurrencyType) in order to refresh user interface. The heavy fetching and updates are done through multiple background managed object contexts (NSPrivateQueueConcurrencyType). No use of children contexts".
I fetch some objects into an array (let us say array of "users"), then i try to update or delete one "user" (the object "user" is obtained from the populated array)in a background context and finally i save that context.
I am listening to NSManagedObjectContextDidSaveNotification and merge any modifications with my main thread managed object context.
Every thing works fine except when i relaunch my application i realize that none of the modifications has been saved.
Here is some code to explain the used pattern
Main managed object context :
-(NSManagedObjectContext *)mainManagedObjectContext {
if (_mainManagedObjectContext != nil)
{
return _mainManagedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
_mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_mainManagedObjectContext setPersistentStoreCoordinator:coordinator];
return _mainManagedObjectContext;
}
Background managed object context :
-(NSManagedObjectContext *)newManagedObjectContext {
NSManagedObjectContext *newContext;
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
newContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[newContext performBlockAndWait:^{
[newContext setPersistentStoreCoordinator:coordinator];
}];
return newContext;
}
Update a record :
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
FootBallCoach *coach = [_coaches objectAtIndex:indexPath.row];
coach.firstName = [NSString stringWithFormat:#"Coach %i",indexPath.row];
NSManagedObjectContext *context = [[SDCoreDataController sharedInstance] newManagedObjectContext];
[context performBlock:^{
NSError *error;
[context save:&error];
if (error)
{
NSLog(#"ERROR SAVING : %#",error.localizedDescription);
}
dispatch_async(dispatch_get_main_queue(), ^{
[self refreshCoaches:nil];
});
}];
}
Am i missing any thing ? should i save my main managed object context after saving the background context ?
If your context is configured with a persistent store coordinator, then save should write data to the store. If your context is configured with another context as parent, then save will push the data to the parent. Only when the last parent, the one that is configured with persistent store coordinator is saved, is the data written to the store.
Check that your background context is really configured with persistent store coordinator.
Check the return value and possible error of the -save:.
Make sure you work with your background context via -performBlock...: methods.
UPDATE
Each time you call your -newManagedObjectContext method, a new context is created. This context knows nothing about FootBallCoach object you’re updating. You need to save the same context FootBallCoach object belongs to.
Don’t forget that each object belongs to one and only one context.
Also make sure you hold a strong reference to a context whose objects you’re using.
I am new to the IOS programming, currently learning core data, I went into running the code where i need to save only specific objects in core data. So how can i do that?
According to the scenario, i have data from server as well as local storage (core data), but when user close the app (went to background) I want to store the data in the server(if net available) if not then in the local storage (but selected only - means specific data should be stored, there are objects which came from online server which i dont want to store on local).
Please let me know any solution if possible.
Regards
Nisar Ahmed
I see two ways to achieve this:
Iterate through inserted and updated objects and revert those you do not wont to save. Inserted objects should be deleted, updated should be refreshed:
for (NSManagedObject* obj in [self.managedObjectContext insertedObjects]) {
if (/*Shouldn't be saved*/) {
[self.managedObjectContext deleteObject:obj];
}
}
for (NSManagedObject* obj in [self.managedObjectContext updatedObjects]) {
if (/*Shouldn't be saved*/) {
[self.managedObjectContext refreshObject:obj mergeChanges:NO];
}
}
Create separate managed object context. Recreate objects that you want to save in new context and then save it.
NSManagedObjectContext* newContext = [[NSManagedObjectContext alloc] init];
[newContext setPersistentStoreCoordinator:[self.managedObjectContext persistentStoreCoordinator]];
for (NSManagedObject* obj in objectsWantToSave) {
NSEntityDescription* entity = [obj entity];
NSDictionary* valuesByKeys = [obj dictionaryWithValuesForKeys:[[entity attributesByName] allKeys]];
NSManagedObject* objCopy = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:newContext];
[objCopy setValuesForKeysWithDictionary:valuesByKeys];
}
[newContext save:NULL];
The second approach is better for my opinion.
Have a look into UIManagedDocument - http://developer.apple.com/library/ios/#documentation/uikit/reference/UIManagedDocument_Class/Reference/Reference.html
It takes care of a lot of the boilerplate involved in using core data.
I'm writing an update for my app, i need to insert inside the db 30 images. Now i'm using core data, and it saves correctly but it takes 10 seconds... So i have decided to send in background the saving process. I have a for cycle that works the image array, each turn i create an NSmanagedObject and i save it. With this base i have try different solution, nothing works correctly:
1-----------------------
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Add code here to do background processing
//context = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSManagedObjectContext *contextTemp = [[NSManagedObjectContext alloc] init];
NSPersistentStoreCoordinator *store=[(RecipesAppDelegate *)[[UIApplication sharedApplication] delegate] persistentStoreCoordinator];
contextTemp.persistentStoreCoordinator=store;
Recipe *recipe = (Recipe *)[contextTemp objectWithID:MoID];
NSManagedObject *image = [NSEntityDescription insertNewObjectForEntityForName:#"Image" inManagedObjectContext:contextTemp];
[image setValue:[[ArrayDizionariImmagini objectAtIndex:i] valueForKey:#"image"] forKey:#"image"];
[image setValue:[NSDate date] forKey:#"data"];
recipe.image=image;
[contextTemp save:nil];
});
This method saves all in the correct way but it takes 10 seconds,just like my first attemp in mainthread,so it's useless...
2-----------------------------------------------------
dispatch_queue_t request_queue = dispatch_queue_create("com.doeatraw.saveimages", NULL);
dispatch_async(request_queue, ^{
// Create a new managed object context
// Set its persistent store coordinator
AppDelegate *theDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *newMoc = [[NSManagedObjectContext alloc] init];
[newMoc setPersistentStoreCoordinator:[theDelegate persistentStoreCoordinator]];
// Register for context save changes notification
NSNotificationCenter *notify = [NSNotificationCenter defaultCenter];
[notify addObserver:self
selector:#selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:newMoc];
BOOL success = [newMoc save:nil];
});
dispatch_release(request_queue);
- (void)mergeChanges:(NSNotification*)notification
{
context = [(RecipesAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
[context performSelectorOnMainThread:#selector(mergeChangesFromContextDidSaveNotification:) withObject:notification waitUntilDone:NO];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
In this attemp i save a temporary ManagedObjectContext only at the end of all the update and then i try to merge changes. In this way i can save all the images in background an i my UI remain responsive all the time. But if i close and i reopen the app all my savings are losts...so it became clear that core data didn't any real merge.
3------------------------------------------------------------
I have also try to direct insert all the data by sql. I have put all in background and it seems ok, ui is responsive and all data are inserted...Anyway something go wrong. When i try to access the same table (by launching other indipendent methods in the app that interrogates the same table) sql crash the app without error in log(only one time i have received the error "Constraint Failed"). I'm able to extract and visualize the inserted images but i cant mad another insert (by core data) in the table.
Someone can help me? I know that NSManagedObjectContext isn't thread safe, but i have tried to follow the documentation guidelines in some of my attemps...Maybe i miss something...
What i'm trying to save is an nsmanagedobject with the image as relationship. How i can real merge my contexts? Or what i can do with the error sqlite Constraint Failed?
Most probably your overhead comes from creating a new context each time you save an image. That's an expensive operation. You only need 2 contexts, one for the main queue and the second for a serial background queue. Also, please note that Apple advises against storing images in the database, the experience I have says the same thing. You should only keep the image path in the database, and keep the image on the file system.
I'm using MagicalRecord 2.0.3 and I can't really figure out how to save data in the background.
According to the documentation, something like this should work:
[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *localContext) {
// Do this hundreds of times
[MyObject createInContext:localContext];
}];
However, nothing is saved to the database. I've seen multiple people posting solutions similar to this:
[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *localContext) {
// Do this hundreds of times
[MyObject createInContext:localContext];
} completion:^{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[[NSManagedObjectContext defaultContext] saveNestedContexts];
}];
}];
This does save my data in the database, however since the save happens on the main thread, my application is unresponsive for a while (with my dataset, about 3 seconds which is way too long).
I've also tried this, but it also blocks up while saving:
self.queue = [[NSOperationQueue alloc] init];
[self.queue addOperationWithBlock:^{
NSManagedObjectContext *localContext = [NSManagedObjectContext contextForCurrentThread];
// Do this hundreds of times
[MyObject createInContext:localContext];
[localContext saveNestedContexts];
}];
And lastly, same blocking effect with this code:
dispatch_queue_t syncQueue = dispatch_queue_create("Sync queue", NULL);
dispatch_async(syncQueue, ^{
NSManagedObjectContext *localContext = [NSManagedObjectContext contextForCurrentThread];
// Do this hundreds of times
[MyObject createInContext:localContext];
[[NSManagedObjectContext contextForCurrentThread] saveNestedContexts];
});
So, what is the best way to solve this? I need to create hundreds of objects in the background and the app needs to remain responsive.
MagicalRecord uses a child context when doing work in the background. This works fine for small changes, but will create excessive main thread blocking when importing large amounts of data.
The way to do it is to use a parallel NSManagedObjectContext and to do the merging yourself with the NSManagedObjectContextDidSaveNotification notification and the mergeChangesFromContextDidSaveNotification method. See performance tests here: http://floriankugler.com/blog/2013/5/11/backstage-with-nested-managed-object-contexts
When saving a nested contexts everything has to be copied to the parent context. As opposed to this, objects that have not been fetched (in the context into which you are merging) will not be merged by mergeChangesFromContextDidSaveNotification. This is what makes it faster.
You might encounter problems if you want to display these results right away after saving in batches and using an NSFetchResultsController. See the following question for a solution:
NSFetchedResultsController with predicate ignores changes merged from different NSManagedObjectContext
For more performance tips take a look at this question: Implementing Fast and Efficient Core Data Import on iOS 5
Create your own context.
NSManagedObjectContext *importContext = [[NSManagedObjectContext alloc]
initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[importContext setPersistentStoreCoordinator:yourPersistentStoreCoordinator];
[importContext setUndoManager:nil]; // For importing you don't need undo: Faster
// do your importing with the new importContext
// …
NSError* error = nil;
if(importContext.hasChanges) {
if(![importContext save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
}
Make sure you are listening for the saves to managed object contexts.
[[NSNotificationCenter defaultCenter]
addObserver:singleton
selector:#selector(contextDidSave:)
name:NSManagedObjectContextDidSaveNotification object:nil];
In the contextDidSave:you merge the change yourself.
- (void) contextDidSave:(NSNotification*) notification
{
if(![notification.object isEqual:self.mainContext]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.mainContext mergeChangesFromContextDidSaveNotification:notification];
});
}
}
Managed object contexts are not thread safe so if you ever need to do any kind of background work with your Coredata objects (i.e. a long running import/export function without blocking the main UI) you will want to do that on a background thread.
In these cases you will need to create a new managed object context on the background thread, iterate through your coredata operation and then notify the main context of your changes.
You can find an example of how this could work here
Core Data and threads / Grand Central Dispatch
I'm developing an application through Core Data and I need to perform some calculation in a background thread to create an xml file based on specific NSManagedObject.
Following the documentation, I set up NSOperation subclass. This class has a property like the following:
#property (nonatomic, retain) NSArray* objectIDs;
where objectIDs is an array of managed object ids (of type NSManagedObjectID). This is necessary according to the documentation: NSManagedObject are not thread safe.
Inside the main of NSOperation subclass I'm doing the following:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSManagedObjectContext *exportContext = [[NSManagedObjectContext alloc] init];
[exportContext setPersistentStoreCoordinator:[self persistentStoreCoordinator]];
for (NSManagedObjectID* objectID in self.orderObjectIDs) {
NSError* error = nil;
Order* order = (Order*)[exportContext existingObjectWithID:objectID error:&error];
// order.someRelationship is fault here...
// Create XML file here...
}
[exportContext reset];
[exportContext release], exportContext = nil;
[[NSNotificationCenter defaultCenter] postNotificationName:kRegisterComplete object:self];
[pool drain];
pool = nil;
Inside the for loop I'm fetching the right object using existingObjectWithID:error method of NSManagedObjectContext class since
Unlike objectWithID:, this method never returns a fault.
The method works. I'm able to retrieve the properties of that retrieved object. The only problem is that relationships are fetched as faults.
Said this, I have two questions.
First, is this the right approach to fecth NSManagedObject in a background thread?
Then, how can I fetch relationships for each fetched object within the for loop? Do I have to create a NSFetchedRequest to fetch the relationship object based on the specific object that has been fetched through the id?
Thank you in advance.
Why do you care that the relationship is a fault? Accessing it will fill the fault and return the data, are you concerned about disk I/O for some reason?
You may try using prefetching to alleviate some of the I/O overhead, but it's utility is limited to the relationship on the entity being fetched:
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/CoreDataFramework/Classes/NSFetchRequest_Class/NSFetchRequest.html
In that case you would create a fetch request with a predicate like so:
[NSPredicate predicateWithFormat:#"SELF == %#", objectID];
That will return to you only the managed object you desire, and with relationship pre-fetching the relationship won't be a fault.
You could further optimize this by using a IN clause and fetching all the objects at once:
[NSPredicate predicateWithFormat:#"SELF IN (%#)", desiredObjectIDs];
You need to make sure of the following:
Before you pass around any object IDs you do - [NSManagedObjectContext obtainPermanentIDsForObjects:error:] on the originating thread.
(on main thread)
[managedObjectContext obtainPermanentIDsForObjects:objectIDs error:nil]
Before attempting to access these objects from another thread (including their relationships) you need to save the NSManagedObjectContext of the originating thread. Otherwise the objects will not exist in persistence and your background thread will be unable to fetch it (or other relationships). Is easiest way to insure this is to call a blocking save on the main thread at the beginning of your NSOperation execution. For example:
dispatch_sync(dispatch_get_main_queue(), {
[managedObjectContext save:nil];
});
You may make an NSFetchRequest or access the relationships directly depending on which way is more convenient; probably accessing the relationships directly. You do not need to call a [NSManagedObjectContext reset] at the end of your routine.