Saving in child context saves to parent context automatically - ios

I'm trying to create an update function that also allows the user to cancel the process.
I'm using the parent-child managedObjectContext and I've set the parent contest concurrency to NSMainQueueConcurrencyType. My plan was to not call the save for parent context to cancel the update. When I tested my theory and commented out the said save lines, I found out the managedObject was still updated. What am I doing wrong?
Partial update function code:
NSManagedObjectContext *bgContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[bgContext setParentContext:self.managedObjectContext];
[bgContext setUndoManager:nil];
[bgContext performBlockAndWait:^{
for (NSDictionary *itemDictionary in items) {
//update cancelled
if (status == -1) {
return;
}
//Function to get a single managedObject by querying the main context.
NSArray *array = [self queryEntity:entityName withResId:[dictionary objectForKey:#"res_id"]];
//get the object id of queried managedobject; context -> mainContext
ENTITY *object = (ENTITY *)[context objectWithID:[[array firstObject] objectID]];
object.data = #"something"; //change data
count++;
//save every 100
if(count%100 == 0) {
[bgContext performBlockAndWait:^{
NSError *error;
if(![bgContext save:&error]) {
NSLog(#"insert error child: %#", [error localizedDescription]);
}
}];
}
}
//save excess
if(count%100 != 0) {
[bgContext performBlockAndWait:^{
NSError *error;
if(![bgContext save:&error]) {
NSLog(#"insert error child: %#", [error localizedDescription]);
}
}];
}
//comment out to prevent saving to parent
/*[context performBlockAndWait:^{
NSError *error;
if(![context save:&error]) {
NSLog(#"insert error parent: %#", [error localizedDescription]);
}
}];*/
}];

Found my problem. Thanks to this post: https://stackoverflow.com/a/7825536/2260928
I use these lines to ignore changes on cancel:
[bgContext reset];
[context reset];

Related

IOS/Objective-C: Background save to core data after NSFetchedResultsController

After performing a sort using NSSortDescriptor on various attributes, I would like to capture the sort order and save it back to core data. To keep the uX steady, I am trying to do this in the background using a separate MOC. However, I'm getting a mutated while enumerated error, possibly because several other things are going on--including syncing with a server and the FRC doing a fetch.
Can anyone see what might be wrong with my code? Thanks in advance for any suggestions.
-(NSFetchedResultsController *)fetchedResultsController {
//code to carry out fetch
//Sort order specified according to parameters
_theItems =[_fetchedResultsController fetchedObjects];
//at this point fetch should be complete
NSManagedObjectContext *mainMOC = _managedObjectContext;
NSManagedObjectContext*privateContext =[[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[privateContext setParentContext:mainMOC];
[privateContext performBlock:^{
for (int i = 0; i < _theItems.count; i++)
{
Items* oneitem = theItems[i];
oneitem.sortorder = i;
}
NSError *error = nil;
if (![privateContext save:&error]) {
NSLog(#"Error saving context: %#\n%#", [error localizedDescription], [error userInfo]);
abort();
}
[mainMOC performBlockAndWait:^{
NSError *error = nil;
if (![mainMOC save:&error]) {
NSLog(#"Error saving context: %#\n%#", [error localizedDescription], [error userInfo]);
//abort();
}
else {
_managedObjectContext = mainMOC;
}
}];
}];
mainMOC=nil;
privateContext = nil;
return _fectchedResultsController;
}
Edit:
From Apple docs:
NSArray *jsonArray = …; //JSON data to be imported into Core Data
NSManagedObjectContext *moc = …; //Our primary context on the main queue
NSManagedObjectContext *private = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[private setParentContext:moc];
[private performBlock:^{
for (NSDictionary *jsonObject in jsonArray) {
NSManagedObject *mo = …; //Managed object that matches the incoming JSON structure
//update MO with data from the dictionary
}
NSError *error = nil;
if (![private save:&error]) {
NSLog(#"Error saving context: %#\n%#", [error localizedDescription], [error userInfo]);
abort();
}
[moc performBlockAndWait:^{
NSError *error = nil;
if (![moc save:&error]) {
NSLog(#"Error saving context: %#\n%#", [error localizedDescription], [error userInfo]);
abort();
}
}];
}];
Edit 2:
Placing the code in viewdidappear stopped the exception. In addition, I changed the code to following and it "seems" to work. Is this now thread safe?
for (int i = 0; i < _theItems.count; i++)
{
Items* oneitem = theItems[i];
NSManagedObjectID* oneID = oneitem.objectID;
Items *myItem = [privateContext objectWithID:oneID];
myItem.sortorder = i;
}
You cannot access NSManagedObjects from multiple contexts. Assuming [NSFetchedResultsController fetchedObjects] returns objects owned by _managedObjectContext, you cannot then access them from privateContext.
One option might be to pass the NSManagedObjectIDs to the private MOC and use the ID to query the main MOC for the corresponding object.
Does that make sense?

MagicalRecord - Retrieve the last inserted record id

I'm using MagicalRecord and I can not understand with and retrieve the id of the record you just saved
Items *item = [Items MR_createEntity];
item.ref_user = ref_user;
[self saveContext];
- (void)saveContext {
[[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) {
if (success) {
DDLogInfo(#"MR_saveContext success");
[self loadView];
[self viewDidLoad];
} else if (error) {
DDLogError(#"Error saving context: %#", error.description);
}
}];
}
Why not just create it out of block?
item = [ITEM MR_createEntityInContext:defaultContext];
[defaultContext MR_saveToPersistentStoreAndWait];
// you can retrieve the id of above item here

controllerDidChangeContent is blocking main thread

When I try to save data for private NSManagedObjectContext I get deadlock even if operations for this context are called inside performBlock: block. This is strange because this block should be performed asynchronosuly withouth interrupting the main UI/UX thread.
Here is the code of this method:
- (void)loadTimetableToCoreData:(id)timetable
{
// Initializing temporary context
NSManagedObjectContext *tempContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
tempContext.parentContext = self.moc;
[tempContext performBlock:^{
// Here is the parsing
NSLog(#"Finished loading to temp MOC");
NSFetchRequest* r = [NSFetchRequest fetchRequestWithEntityName:#"Timetable"];
[r setIncludesPendingChanges:NO];
NSArray *existingTimetables = [tempContext executeFetchRequest:r error:nil];
for (Timetable *table in existingTimetables) {
[tempContext deleteObject:table];
}
// Saving procedure with multithreading
NSError *error;
if (![tempContext save:&error]) {
NSLog(#"Couldn't save: %#", [error localizedDescription]);
}
NSLog(#"Finished saving to temp MOC");
[self.moc performBlock:^{
// Save groups to presistant store
NSError *error;
if (![self.moc save:&error]) {
NSLog(#"Couldn't save: %#", [error localizedDescription]);
}
NSLog(#"Finished saving to main MOC");
[self.writer performBlock:^{
// Save groups to presistant store
NSError *error;
if (![self.writer save:&error]) {
NSLog(#"Couldn't save: %#", [error localizedDescription]);
}
NSLog(#"Finished saving to writer MOC");
}];
}];
}];
}
As you can see there are some NSLogs inside this method and to visualize my problem I get stuck between Finished loading to temp MOC and Finished saving to temp MOC.
What am I doing wrong and how to unlock the main thread?
Update
Here is the screen from Instruments and what I can see... The controllerDidChangeCintent is killing everything.
I know that this is caused by those lines from loadTimetableToCoreData:
NSFetchRequest* r = [NSFetchRequest fetchRequestWithEntityName:#"Timetable"];
[r setIncludesPendingChanges:NO];
NSArray *existingTimetables = [tempContext executeFetchRequest:r error:nil];
for (Timetable *table in existingTimetables) {
[tempContext deleteObject:table];
}
But I can't get rid of them! Or I don't know how....

App crashes when saving in different thread

I have a method which creates a separate thread:
// Create thread
dispatch_queue_t uniqueQueue = dispatch_queue_create("Unique Email Queue", NULL);
// Run block on another thread called downloadQueue
dispatch_async(uniqueQueue, ^{
// Save to core data for redundancy
User *coreDataUser = [NSEntityDescription insertNewObjectForEntityForName:#"User" inManagedObjectContext:self.managedObjectContext];
coreDataUser.username = [emailStr lowercaseString];
coreDataUser.email = emailStr;
coreDataUser.name = nameStr;
NSError *error;
if (![self.managedObjectContext save:&error])
{
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
}
The app always crashes on this line:
User *coreDataUser = [NSEntityDescription insertNewObjectForEntityForName:#"User" inManagedObjectContext:self.managedObjectContext];
I am using this tutorial as a reference: http://www.codigator.com/tutorials/ios-core-data-tutorial-with-example/
What am I missing?
NSManagedObjectContext is not threadsafe, try to create new moc in side of the async block.
Try:
// Create thread
dispatch_queue_t uniqueQueue = dispatch_queue_create("Unique Email Queue", NULL);
// Run block on another thread called downloadQueue
dispatch_async(uniqueQueue, ^{
// Save to core data for redundancy
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator:persistentStoreCoordinator];
User *coreDataUser = [NSEntityDescription insertNewObjectForEntityForName:#"User" inManagedObjectContext:context];
coreDataUser.username = [emailStr lowercaseString];
coreDataUser.email = emailStr;
coreDataUser.name = nameStr;
NSError *error;
if (![context save:&error])
{
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
}
The persistent store is in your AppDelegate
Remember - CoraData is not thread safe !!!!!
If you want separate thread for your managedObjectContext you have to create one on this thread. To call proper context you have to run it with performBlock block. In your case:
[self.managedObjectContext performBlock:^{
User *coreDataUser = [NSEntityDescription insertNewObjectForEntityForName:#"User" inManagedObjectContext:self.managedObjectContext];
coreDataUser.username = [emailStr lowercaseString];
coreDataUser.email = emailStr;
coreDataUser.name = nameStr;
NSError *error;
if (![self.managedObjectContext save:&error])
{
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
}];
But remember, if you creating context on mainThread, your block is also executed on mainThread.

How to reset CoreData Application

I am trying, to reset my app which uses Core Data, for this, I have written the code below
- (void) resetApplicationModel
{
// NSError *error;
NSError *error;
// retrieve the store URL
NSURL * storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"abc.sqlite"];
// lock the current context
[self.managedObjectContext lock];
[self.managedObjectContext reset];//to drop pending changes
//delete the store from the current managedObjectContext
if ([[self.managedObjectContext persistentStoreCoordinator] removePersistentStore:[[[self.managedObjectContext persistentStoreCoordinator] persistentStores] lastObject] error:&error])
{
// remove the file containing the data
if([[NSFileManager defaultManager] removeItemAtURL:storeURL error:&error])
{
//recreate the store like in the appDelegate method
if([[self.managedObjectContext persistentStoreCoordinator] addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error] == nil)
{
NSLog( #"could not create new persistent store at %# - %#", [storeURL absoluteString], [error localizedDescription]);
}
}
else
{
NSLog( #"could not remove the store URL at %# - %#", [storeURL absoluteString], [error localizedDescription]);
}
}
else
{
NSLog( #"could not remove persistent store - %#", [error localizedDescription]);
}
[self.managedObjectContext unlock];
__managedObjectContext = nil;
[self managedObjectContext];
_rootViewController.controller = nil;//Make NSFetchResultsController nil;
}
This code works fine on ios 6.1 simulator and there are no issues, but when I check this on iphone 4 running iOS 6.1.3, the persistence file is not deleted and I get error saying
"could not remove the store URL at file://localhost/var/mobile/Applications/147E198E-FAB9-46EA-BE4B-4411AFA013FB/Documents/abc.sqlite - The operation couldn’t be completed. (Cocoa error 4.)"
But all the data is deleted, Now when I try to add new Data inside coreData , I get a error saying
" Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'This NSPersistentStoreCoordinator has no persistent stores. It cannot perform a save operation.'"
Please help me guys
Regards
Ranjit
Create a category on NSManagedObjectContext then implement:
- (BOOL)deleteAllObjectsForEntityName:(NSString *)entity withError:(NSError *__autoreleasing *)error {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:entity inManagedObjectContext:self]];
[request setIncludesPropertyValues:NO];
NSArray *allObjects = [self executeFetchRequest:request error:error];
if (![self save:error])
return NO;
for (NSManagedObject *object in allObjects)
[self deleteObject:object];
return YES;
}
Then to use this simply do:
- (void)deleteAllObjectsForEntityName:(NSString *) entity {
NSError *error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil) {
if (![managedObjectContext deleteAllObjectsForEntityName:entity withError:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
}
}
Pass your entity name in and it will delete it all :-)

Resources