Core data not saving my data - ios

I'm using core data to save some integer (rate) and then I call save in the context:
HeartRateBeat * beat = [HeartRateBeat heartRateWithHeartRate:rate
ofRecordTitle:self.recordTitle
inManagedObjectContext:document.managedObjectContext];
NSError * error;
[document.managedObjectContext save:&error];
Inside that convenient method I create the object using NSEntityDescription like this:
heartRateBeat = [NSEntityDescription insertNewObjectForEntityForName:#"HeartRateBeat" inManagedObjectContext:context];
(I only copied some important code, just to show what I did.)
I immediately execute a fetch request after every single heart beat inserted and managed object context saved (I save immediately), and the request shows that heart beat does appear to be stored inside Core Data (with growing array of heart beats), but if I restart my app (I'm using simulator BTW) I know things aren't actually getting saved to disk because it starts anew. Checking with SQLite3 command line shows empty tables. What am I missing here?

I get the same problem but I think its just because, I assume, like me you are just stopping the app through xcode and not actually closing it down. I use this code to force a write. Im using a UIManagedDocument, shared through appdelegate, rather than setting everything up manually.
NSError *error = nil;
if (![self.managedObjectContext save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
[[AppDelegate sharedAppDelegate].userDatabase saveToURL:[AppDelegate sharedAppDelegate].userDatabase.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:nil];

I don't know about you guys, but when I want my Core Data to save, my Core Data better save.
Here's some code that will for sure save all of your Core Datas.
-(void)forceSave {
NSManagedObjectContext * context = self.managedObjectContext; //Get your context here.
if (!context) {
NSLog(#"NO CONTEXT!");
return;
}
NSError * error;
BOOL success = [context save:&error];
if (error || !success) {
NSLog(#"success: %# - error: %#", success ? #"true" : #"false", error);
}
[context performSelectorOnMainThread:#selector(save:) withObject:nil waitUntilDone:YES];
[context performSelector:#selector(save:) withObject:nil afterDelay:1.0];
[context setStalenessInterval:6.0];
while (context) {
[context performBlock:^(){
NSError * error;
bool success = [context save:&error];
if (error || !success)
NSLog(#"success: %# - error: %#", success ? #"true" : #"false", error);
}];
context = context.parentContext;
}
NSLog(#"successful save!");
}
Note that this is BAD CODE. Among other problems, it's not thread-safe and takes too long. However, try using this and deleting some parts of it to your satisfaction :)

Related

Core Data concurrency with NSPersistentContainer

NOTE: I've looked at similar questons, but didn't find one that describes this situation.
I'm looking at the following example code from Apple regarding Core Data concurrency (https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CoreData/Concurrency.html)
NSArray *jsonArray = …;
NSPersistentContainer *container = self.persistentContainer;
[container performBackgroundTask:^(NSManagedObjectContext *context) {
for (NSDictionary *jsonObject in jsonArray) {
AAAEmployeeMO *mo = [[AAAEmployeeMO alloc] initWithContext:context];
[mo populateFromJSON:jsonObject];
}
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Failure to save context: %#\n%#", [error localizedDescription], [error userInfo]);
abort();
}
}];
In my app, the save is not initiated until the user taps the save button on the screen. How do I go about it, should I use a child context instead for that situation, where the private context is a property of the VC?
NSArray *jsonArray = …; //JSON data to be imported into Core Data
NSManagedObjectContext *moc = self.persistentContainer.viewContext; //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 = …; // WHICH CONTEXT TO USE? <<<======
//update MO with data from the dictionary
}
NSError *error = nil;
if (![private save:&error]) {
NSLog(#"Error saving context: %#\n%#", [error localizedDescription], [error userInfo]);
abort();
}
}
And then once the user taps save do this:
NSManagedObjectContext *moc = self.persistentContainer.viewContext; //Our primary context on the main queue
[moc performBlockAndWait:^{
NSError *error = nil;
if (![moc save:&error]) {
NSLog(#"Error saving context: %#\n%#", [error localizedDescription], [error userInfo]);
abort();
}
}];
}];
Also note the question which moc to use in the example above (<<<=====)
EDIT: What I ended up doing in the end is save the child context immediately so that the table only uses viewContext to display the results. If the user then exits without saving, I delete all the results again from the viewContext. The save button is still there, but now only sets a flag indicating not to delete the results.
If you have one page of forms and you want it to save when the user presses the save button, then simply take the data from the textFields (or whatever your data input is) and put it into core data using performBackgroundTask. Since the data is only stored in the textFields while the user is editing if the user pushes back his edits will be lost.
If you have a lots of changes to a complex document with lots of different entities that the user can create or destroy or link and all of that is only saved when the user presses save then you should use a child context. You would display the data based on the values in the child context but only push those changes to the parent context if the user presses save. This is a very rare situation and I have never personally encountered the need to do this.
I strongly suspect that you are in the first case. Don't use child context. Use performBackgroundTask and save the data when the user presses save.
(also the correct context to use inside the block [private performBlock:^{ is the private context)

Private NSManagedObjectContexts and deleting objects

I have a Core Data stack with a main managed object context with NSMainQueueConcurrencyType.
The user can initiate a task on a managed object that can take a long time, so it is performed on a separate context:
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[context setParentContext:mainMOC];
Person *samePerson = (Person *)[context objectWithID:person.objectID];
[context performBlock:^{
// BLOCK 1
// do lots of work
// then update the managed object
samePerson.value = someCalculatedValue;
// save the private context
NSError *error;
if (![context save:&error]) {
NSLog(#"Error: %#", error);
}
[mainMOC performBlock:^{
NSError *error;
if (![mainMOC save:&error]) {
NSLog(#"Error saving: %#", error);
}
}];
}];
This works fine, and the main MOC gets updated properly, NSFetchedResultsController hooked up to it perform properly, etc.
The problem is with deleting. I have this setup to delete objects:
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[context setParentContext:mainMOC];
[context performBlock:^{
// BLOCK 2
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:#"Person"];
NSError *error;
NSArray *all = [context executeFetchRequest:request error:&error];
if (!all) {
NSLog(#"Error fetching: %#", error);
} else {
for (NSManagedObject *person in all) {
[context deleteObject:person];
}
NSError *error;
if (![context save:&error]) {
NSLog(#"Error saving: %#", error);
}
[mainMOC performBlock:^{
NSError *error;
if (![mainMOC save:&error]) {
NSLog(#"Error saving: %#", error);
}
}];
}
}];
Now if I do this delete operation (Block 2) during the time it takes to perform the long-duration task (Block 1), then the delete operation finishes quickly, and saves to main context. After a while Block 1 finishes, and when it saves the mainMOC at its end, we get a seemingly obvious crash:
CoreData could not fulfill a fault for ...
My question is: how do I perform a task such as Block 1 with the possibility of its object being deleted?
If these are both background tasks that cannot run at the same time, try using semaphores to protect access to them.
eg. for an instance variable:
dispatch_semaphore_t _backgroundProcessingSemaphore;
Lazily initialised using something like:
- (dispatch_semaphore_t)backgroundProcessingSemaphore
{
if (!_backgroundProcessingSemaphore) {
_backgroundProcessingSemaphore = dispatch_semaphore_create(1);
}
return _backgroundProcessingSemaphore;
}
Surround the critical code with:
dispatch_semaphore_wait(self.backgroundProcessingSemaphore, DISPATCH_TIME_FOREVER);
// Critical code
dispatch_semaphore_signal(self.backgroundProcessingSemaphore);
Only one critical section of code can then run at any point in time. The block that calls dispatch_semaphore_wait will block if the semaphore is already taken, until it is freed up.
You also probably want to think about splitting your long-duration task up so that it will run in discrete batches if you're not already doing so - this is useful if the long running background task timer is about to expire while you still have work to do - you can stop and restart from the appropriate point on next launch.
Other options would involve forcing a save on block 1 before block 2 saves itself, but this starts to get messy. Much easier to ensure the two competing blocks cannot overlap.

Difficulty in creating Core Data managed objects

At the moment I can't see any AUser objects in my sqlite3 app database.
This is the code I have to create a user. Am I missing something? There are no warnings/errors with the code.
AUser *user = [NSEntityDescription insertNewObjectForEntityForName:#"AUser" inManagedObjectContext:_managedObjectContext]; // _managedObjectContext is declared elsewhere
user.name = username; //username is some string declared elsewhere / name is an attribute of AUser
You need to perform a save on the context.
NSError* error = nil;
if(![context save:&error]) {
// something went wrong
NSLog(#"Error saving context: %#\n%#", [error localizedDescription], [error userInfo]);
abort();
}
When you perform a save, data in the context are saved to the persistent store coordinator (hence in your sql store).
P.S. Based on the discussion with #G. Shearer, in production if the context fails to save, you can handle the error gracefully. This means not using abort() call that causes the app to crash.
NSError* error = nil;
if(![context save:&error]) {
// save failed, perform an action like alerting the user, etc.
} else {
// save success
}
You need to call save after creating the object.
Example:
NSError *error;
if ([user.managedObjectContext save:&error]) {
//Success!
} else {
//Failure. Check error.
}
you need to call
NSError *error = nil;
[_managedObjectContext save:&error];
if(error != nil) NSLog(#"core data error: %#",[[error userInfo] description]);
before the object will be persisted to the database

ios - Core data - app crash if deleting before save finished

My app simply add some users informations (name, birthdate, thumbnail, ...) with Core Data.
I noticed that if I delete a user right after created it, my app just stop working (not a crash, xCode returns no crash log, nothing).
I'm using asynchronous nested context for saving my users informations so I guess that behavior is due to the fact that my delete statement is executing before my save statement.
But since i'm a total beginner with Core Data, i don't really know how to handle that. I don't even know if i declared nested contexts the right way.
Here's my save codes :
NSManagedObjectContext *tmpContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
tmpContext.parentContext = self.backgroundManagedObjectContext;
BSStudent *newStudent = (BSStudent *)[NSEntityDescription insertNewObjectForEntityForName:kBSStudent inManagedObjectContext:tmpContext];
newStudent.firstname = firstname;
newStudent.lastname = lastname;
newStudent.birthdate = birthdate;
newStudent.thumbnail = thumbnail;
newStudent.createdAt = [NSDate date];
[self dismissViewControllerAnimated:YES completion:nil];
[tmpContext performBlock:^{
[tmpContext save:nil];
[self.backgroundManagedObjectContext performBlock:^{
NSError *error;
if (![self.backgroundManagedObjectContext save:&error]) {
NSLog(#"%#", [error localizedDescription]);
}
[self.managedObjectContext performBlock:^{
NSError *error;
if (![self.managedObjectContext save:&error]) {
NSLog(#"%#", [error localizedDescription]);
}
}];
}];
}];
For precision, self.managedObjectContext is a NSPrivateQueueConcurrencyType and self.backgroundManagedObjectContext is a NSMainQueueConcurrencyType. And self.backgroundManagedObject is a child of self.managedObjectContext.
Here's my delete codes :
BSStudent *student = objc_getAssociatedObject(alertView, kDeleteStudentAlertAssociatedKey);
// on supprimer l'objet et on sauvegarde le contexte
[self.managedObjectContext deleteObject:student];
NSError *error;
if(![self.managedObjectContext save:&error]) {
NSLog(#"%#", [error localizedDescription]);
}
Can someone know how to handle this situation properly ?
Your delete is probably using the BSStudent created by a different context than you are deleting with. The following code will fix that.
NSManagedObjectContext * deleteContext = student.managedObjectContext;
[deleteContext deleteObject:student];
If you really want to use the other context, refetch the student using ObjectID
NSManagedObject * studentToDelete = [self.managedObjectContext objectWithID:student.objectID];
[self.managedObjectContext deleteObject:studentToDelete];
Nested contexts tips
Your contexts are probably okay, but I see a lot of people throwing around performBlock unnecessarily. With nested contexts, the QueueConcurrencyType refers to the thread it will do Core Data operations on, not the thread it was created on. So doing an operation like save on itself inside its performBlock is unnecessary and can lead to deadlocks.
When you save a child context, the parent is automatically synced with the changes. If you want to save upwards to the next higher parent automatically, I would recommend registering the parent for NSManagedObjectContextDidSaveNotification of the child saves. You can make this easier by having your AppDelegate have a factory method for creating the child contexts.
- (NSManagedObjectContext *)createChildContext
{
NSManagedObjectContext *tmpContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
tmpContext.parentContext = self.managedObjectContext;
//Register for NSManagedObjectContextDidSaveNotification
return tmpContext;
}
if you wrap your delete in a performBlock call it can't execute at the same time as the saving performBlock.
e.g.:
BSStudent *student = objc_getAssociatedObject(alertView, kDeleteStudentAlertAssociatedKey);
// on supprimer l'objet et on sauvegarde le contexte
[self.managedObjectContext performBlock:^{
[self.managedObjectContext deleteObject:student];
NSError *error;
if(![self.managedObjectContext save:&error]) {
NSLog(#"%#", [error localizedDescription]);
}
}];
This is the "preferred" way of dealing with contexts as it serializes access to the context and keeps all those operations on the contexts thread,
I assume you are getting the crash because the objectID is becoming invalid or changing before the save completes, near the top of the call stack you'll see something about "hash64" or such

CoreData delete save Error

[request2 setEntity:entity];
NSPredicate * predicate2 = [ NSPredicate predicateWithFormat:#"logoFrameNum == %#",[NSNumber numberWithInt:7]];
[request2 setPredicate:predicate2];
NSManagedObject * collectionList2 = [[ managedObjectContext executeFetchRequest:request2 error:&error2] objectAtIndex:0];
NSLog(#"context :%#", deleteContext1);
[managedObjectContext deleteObject:collectionList2];
BOOL yesorno = [collectionList2 isDeleted];
NSLog(#"yesorno : %i", yesorno);
NSError * error10;
NSLog(#"[managedObjectContext ] : %#", deleteContext1);
[collectionList2 release];
if (![managedObjectContext save:&error10]) {
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error10, [error userInfo]);
exit(-1); // Fail
}
There is much more source above it. Change variables or get data from coredata is well performed with the same NSManagedObjectContex I have there. However delete with that context makes me crazy. It crashes without any error message just in
if (![managedObjectContext save:&error10]) {
I tried get a new context and so on and on........a lot..
You are performing a release on an object (collectionList2) that you don't own. This may cause a crash later on (for example, during the save). Try removing the release.
Maybe you are trying to delete a nil object.
Also, you should do all this within one single NSManagedObjectContext.
Try putting your save:error: method right below the deleteObject: call.

Resources