App crashes when saving in different thread - ios

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.

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?

How to update coredata value in Objective-C

I am new in iOS and I am facing a problem regarding to update value of coredata.
For Save
NSManagedObjectContext *context = [self managedObjectContext];
NSManagedObject *device;
if (self.device) {
// Update existing device
[device setValue:GlobalIndexPath forKey:#"key"];
} else {
// Create a new device
NSManagedObject *newDevice = [NSEntityDescription insertNewObjectForEntityForName:#"Device" inManagedObjectContext:context];
[newDevice setValue:GlobalIndexPath forKey:#"key"];
}
NSError *error = nil;
// Save the object to persistent store
if (![context save:&error]) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
My code to fetch core data is
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"EntityName" inManagedObjectContext:context]];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
And to update I and using code
NSManagedObject* favoritsGrabbed = [results objectAtIndex:0];
[favoritsGrabbed setValue:#"1" forKey:#"Key"];
Update code not update it add one object.
Note - GlobalIndexPath is a name of string.
But this is not working for me any suggestion. Thanks in Advcance!
You need to save the context every time you make changes to any NSManagedObject and want it to persist. Try this:
NSManagedObject* favoritsGrabbed = [results objectAtIndex:0];
[favoritsGrabbed setValue:#"1" forKey:#"Key"];
NSError *error = nil;
// Save the object to persistent store
if (![context save:&error]) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}

How to delete selected tables using persistentStoreCoordinator

I'm currently working on an IOS app which is already developed using objective-C.
I have added a module where users when login store details about the user. But as the app is already having some code, when I press the logout button it deletes all the entities from the database. For this they are using something like the code below.
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSError *error = nil;
// retrieve the store URL
NSURL *storeURL = [[managedObjectContext persistentStoreCoordinator] URLForPersistentStore:[[[managedObjectContext persistentStoreCoordinator] persistentStores] lastObject]];
// lock the current context
[managedObjectContext lock];
[managedObjectContext reset];//to drop pending changes
//delete the store from the current managedObjectContext
if ([[managedObjectContext persistentStoreCoordinator] removePersistentStore:[[[managedObjectContext persistentStoreCoordinator] persistentStores] lastObject] error:&error]){
// remove the file containing the data
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:&error];
//recreate the store like in the appDelegate method
[[managedObjectContext persistentStoreCoordinator] addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error];//recreates the persistent store
}
[managedObjectContext unlock];
By keeping the break points, I understood that they are retrieving the url of the database and deleting it and re-creating it. Lets say they have 3 tables A,B and C, I want to delete A & B but not C. Reference- Persistent Store Coordinator
Is my understanding correct? How can I achieve this?
TIA
Try this code
NSManagedObjectContext *context = [self managedObjectContext];
[context deleteObject:managedObject];
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Can't Delete! %# %#", error, [error localizedDescription]);
return;
}
When user tap logout, you should clear the stored data.
First of all Fetch all the data using simple fetch store it in an array say "results"
then fetch objects in the array and remove it
for (Entity *entityOBJ in results) {
[context deleteObject:entityOBJ];
}
[context save:&error];
You have 3 tables, repeat it for 3 tables. Create a function just pass on the TableName.
I did something like below. Correct me If I'm wrong with my approach.
- (void) deleteData {
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSError *error = nil;
[managedObjectContext lock];
[managedObjectContext reset];
NSPersistentStoreCoordinator *psc = [managedObjectContext persistentStoreCoordinator];
NSManagedObjectModel *managedModel = [psc managedObjectModel];
NSArray *allEntityNames = [managedModel.entitiesByName allKeys];
for(NSString *entityName in allEntityNames)
{
//I wanted to delete all objects except for one table
if(![entityName isEqual:switchAccountsTableName])
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc]init];
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSError *objError = nil;
NSArray *fetchedObjects = [managedObjectContext executeFetchRequest:fetchRequest error:&objError];
if(fetchedObjects == nil)
{
NSLog(#"Logout- Couldnt delete entity objects");
}
for(NSManagedObject *entityObj in fetchedObjects)
{
[managedObjectContext deleteObject:entityObj];
}
}
}
[managedObjectContext save:&error];
[managedObjectContext unlock];
}

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....

Saving in child context saves to parent context automatically

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];

Resources