Core Data was mutated while being enumerated - ios

I've an annoying problem with Core Data. My app need to get contacts from iPhone and save it in my database. I'm trying to do that in background thread.
I use above code for that:
[self performSelectorInBackground:#selector(fetchingContact) withObject:nil];
-(void)fetchingContact{
// Some Code
for (int i = 0; i < nPeople; i++)
{
//Some Code
NSManagedObjectContext *context = [APP_DELEGATE managedObjectContext];
ABRecordID recordID = ABRecordGetRecordID(person);
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Contact" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:#"(contactId = '%d')",recordID]];
[fetchRequest setPredicate:predicate];
NSError *error = nil;
//crash
NSArray *contactObjArray = [context executeFetchRequest:fetchRequest error:&error];
//crash
if (error) {}
Contact *contacts;
if (contactObjArray.count == 0) {
contacts = [NSEntityDescription insertNewObjectForEntityForName:#"Contact" inManagedObjectContext:context];
}else {
contacts = [contactObjArray objectAtIndex:0];
}
//Some Code
}
}
In AppDelegate:
- (NSManagedObjectContext *)managedObjectContext
{
NSLog(#"managedObjectContext");
// Returns ;the managed object context for the application (which is already bound to the persistent store coordinator for the application.)
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (!coordinator) {
return nil;
}
_managedObjectContext = [[NSManagedObjectContext alloc]initWithConcurrencyType: NSMainQueueConcurrencyType];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
return _managedObjectContext;
}
Here I try to save in my Core Data but it crash with error :
* Terminating app due to uncaught exception 'NSGenericException', reason: '* Collection <__NSCFSet: 0x15fad880> was mutated while being enumerated.'` at this line:
NSArray *contactObjArray = [context executeFetchRequest:fetchRequest error:&error];
I search already online, I found a lot of things but nothing helps me. When I run that, there is no place where Core Data is changed, or Contact entity. That does this error very strange.
If I run it in main thread I get no errors/ no crash, but if the app is quit in that time (while is executed) I lose all content from Contact
Please, any help. Tell me if I need to provide more information.

This error happen when you are modifying core data while you try to get them.
That also could be cause of the loop you're doing, you are inserting a new object in coredata without saving before you do an other retrieve. Try saving your managedobjectcontext :
favorite
I have an annoying problem with Core Data. My app need to get contacts from iPhone and save it in my database. I'am trying to do that in background thread.
I use above code for that:
[self performSelectorInBackground:#selector(fetchingContact) withObject:nil];
-(void)fetchingContact{
// Some Code
for (int i = 0; i < nPeople; i++)
{
//Some Code
NSManagedObjectContext *context = [APP_DELEGATE managedObjectContext];
ABRecordID recordID = ABRecordGetRecordID(person);
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Contact" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:#"(contactId = '%d')",recordID]];
[fetchRequest setPredicate:predicate];
NSError *error = nil;
NSArray *contactObjArray = [context executeFetchRequest:fetchRequest error:&error];
if (error) {}
Contact *contacts;
if (contactObjArray.count == 0) {
contacts = [NSEntityDescription insertNewObjectForEntityForName:#"Contact" inManagedObjectContext:context];
[context save:&error];
}else {
contacts = [contactObjArray objectAtIndex:0];
}
//Some Code
}
}
If that doesn't solve your problem, maybe check if your method is called multiple times.

Related

How to get single attribute Value of entity in Coredata?

I am having Entity Called EY_Appliances with Attributes applianceId ,applianceName,watts,amountPerWatts.Also i am Having Arrays like this:
Prefrence.h
#define APPLIANCENAME_ARRAY #[#"Fridge",#"Microwave",#"Mixie",#"Roti Maker",#"Coffee Machine",#"Dish Washer",#"Wet Grinder",#"Electric Stove",#"Ceiling Fan",#"TV",#"Table Fan",#"Tubelight",#"Bulb",#"AC",#"Vacuum Cleaner",#"CFL",#"LED",#"Washing Machine",#"Toaster",#"Room Heater",#"Iron",#"Motor",#"Water Heater",#"Inverter / UPS",#"Air Cooler",#"Steamer / Air Fryer",#"Hair Dryer",#"Laptop",#"PC",#"Tablet",#"Router / Modem",#"Home Theatre",#"Projector",#"PS3/PS4/XBOX"]
#define WATTS_ARRAY #[#"120",#"1000",#"700",#"30",#"167",#"810",#"180",#"150",#"75",#"120",#"75",#"40",#"60",#"1200",#"1400",#"20",#"6",#"300",#"1000",#"1600",#"1400",#"2400",#"1000",#"67",#"173",#"585",#"1026",#"15",#"150",#"4",#"4",#"17",#"240",#"10"]
DataAccessHandler.m
-(void)storeApplianceDetailsToEntity
{
NSManagedObjectContext *context = [self managedObjectContext];
for (int i=0; i<APPLIANCENAME_ARRAY.count; i++)
{
NSManagedObject *object = [NSEntityDescription insertNewObjectForEntityForName:#"EY_Appliances" inManagedObjectContext:context];
[object setValue: APPLIANCENAME_ARRAY[i] forKey:#"applianceId"];
[object setValue: APPLIANCENAME_ARRAY[i] forKey:#"applianceName"];
[object setValue: WATTS_ARRAY[i] forKey:#"watts"];
[object setValue: WATTS_ARRAY[i] forKey:#"amountPerWatts"];
}
NSError *error = nil;
if (![context save:&error])
{
NSLog(#"Saving Failed with error %#", error);
}
NSLog(#"entityValue==>%#",context);
}
-(NSArray *) fetchApplianceDetailsFromEntity:(NSString *) entityName
{
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest;
NSEntityDescription *entityDescription;
NSArray *array;
fetchRequest = [[NSFetchRequest alloc] init];
entityDescription = [NSEntityDescription entityForName:entityName inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entityDescription];
array = [managedObjectContext executeFetchRequest:fetchRequest error:nil];
NSLog(#"arr==>>%#",array);
return array;
}
ViewController.m
-(void)viewDidLoad
{
DataAccessHandler *dataAccess=[[DataAccessHandler alloc]init];
[dataAccess storeApplianceDetailsToEntity];
NSArray *applianceData = [dataAccess fetchApplianceDetailsFromEntity:#"EY_Appliances"];
//How to print the applianceName,If i write Like this applianceData.applianceName,it shows error…
}
1.How to print the applianceName?
2.How to set the applianceId primaryKey and store the value for applianceId 1 to 34?
NSDictionary *applianceData = [dataAccess fetchApplianceDetailsFromEntity:#"EY_Appliances"];
use keyword get back value
applianceData is an array, so you need to access it through an index
Your code just now is using the appliance name as ID - if all you need is an identifier, you can use your index i
The load returns everything into the array - if you only want to load part of the dataset, you need to add a fetch predicate in fetchApplianceDetailsFromEntity
[fetch setPredicate:[NSPredicate predicateWithFormat:#"(applianceId == %#)",idString]];

Core-Data executeFetchRequest freezes App in a background thread

I have a saveMOC which is the direct parent of a mainMOC, and I need for online fetch another tmpMOC in order not to block my UI whilst fetching a ton of records from the Internet.
My app freezes.
I could narrow it to this very point:
fetchedItems = [tmpMOC executeFetchRequest:fetchRequest error:&error];
I tried to enclose this within dispatch_sync, [moc.parentcontext.parentcontext.persistentStoreCoordinator lock/unlock], [whatevermoc performBlockAndWait]...
I also try to fetch the _mainMOC... No way...
I understand that executeFetchRequestis not thread safe, so, how do I lock whatever I need to lock to get sure I am not inserting a double?
Anybody could help?
UPDATE (1)
_saveMOC instantiation in AppDelegate:
- (NSManagedObjectContext *)managedObjectContext
{
if (_mainMOC != nil) {
return _mainMOC;
}
if (_saveMOC == nil) {
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_saveMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_saveMOC setPersistentStoreCoordinator:coordinator];
[_saveMOC setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
}
}
if (_mainMOC == nil) {
_mainMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_mainMOC setParentContext:_saveMOC];
[_mainMOC setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(saveContextChanges:) name:NSManagedObjectContextDidSaveNotification object:_mainMOC];
temporaryMOCcreation in MainViewController:
NSManagedObjectContext *temporaryContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
temporaryContext.parentContext = _mainMOC;
[temporaryContext performBlock:^{ //AndWait
NSError *error=nil;
Feed *feed=(Feed *)[temporaryContext existingObjectWithID:feedID error:&error];
//...
[RSSParser parseRSSFeedForRequest:req success:^(NSArray *feedItems)
{
NSLog(#"[MasterViewController::fetchPosts] inserting %d Posts.../OK", (int)[feedItems count]);
feed.valid = [NSNumber numberWithBool:YES];
for(RSSItem *i in feedItems)
{
[self createPostInFeed:feed withTitle:i.title withContent:(i.content?i.content:i.itemDescription) withURL:[i.link absoluteString] withDate:(i.pubDate?i.pubDate:[NSDate date]) inMOC:temporaryContext];
}
[[NSNotificationCenter defaultCenter] postNotificationName:#"NewPostsFetched" object:f];
Then the freeze happens in createPostInFeed:
// #synchronized(fetchRequest) { // THIS DOESN'T CHANGE ANYTHING
// [moc.persistentStoreCoordinator lock]; // THIS NEITHER
NSArray *fetchedItems;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *ent = [NSEntityDescription entityForName:#"Post" inManagedObjectContext:moc];
fetchRequest.entity = ent;
fetchRequest.propertiesToFetch = [NSArray arrayWithObjects:#"title", #"url", #"feed.name", nil];
[fetchRequest setResultType:NSDictionaryResultType];
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"title == %# AND url == %# AND feed.rss == %#", title, url, feed.rss];
NSError *error = nil;
fetchedItems = [moc executeFetchRequest:fetchRequest error:&error]; // FREEZES HERE
// [moc.persistentStoreCoordinator unlock]; // THIS DOESN'T CHANGE ANYTHING
// } // Synchronized neither...
Update (2):
Here's what the Time Profiler sees.
Update (3):
Block edited:
NSUInteger fetchedItems;
NSString *feedRSS=feed.rss;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *ent = [NSEntityDescription entityForName:#"Post" inManagedObjectContext:moc];
fetchRequest.entity = ent;
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"title == %# AND url == %# AND feed.rss == %#", title, url, feedRSS];
NSLog(#"MainViewController::createPostInFeed> Before fetchRequest for %# (%#)", title, url);
NSError *error = nil;
fetchedItems = [moc countForFetchRequest:fetchRequest error:&error];
NSLog(#"MainViewController::createPostInFeed> After fetchRequest for %# (%#)", title, url); // THIS NSLOGGING NEVER HAPPENS
Update (4):
New profiler pic with call tree inverted.
What is your goal with createPostInFeed? You show that you are doing a fetch but what do you do with that fetch? Is this a "insert or update" check? or is it just a "insert or skip" test?
Any fetch is going to lock the NSPersistentStoreCoordinator and cause your application to potentially lock up while the fetch is being performed. There are ways to mitigate that:
Run Instruments
Make your fetches more efficient
If you don't need objects (in a insert or skip test) then do a count instead
Fetch on a background queue and make sure your main MOC has all the objects it needs to avoid a lock
What does your instruments profile show you?
What does createPostInFeed do with the results of that fetch?

Having only one instance of an entity in Core Data

I am making an application where you need to log in with a 4 digit password but there can only be one password at a time. I am trying to save it to core data but whenever the user adds a new password it just adds it to the long list. How can I restrict an entity to only have one instance of itself?
Here is my code just in case it will help:
-(BOOL)savePassword:(NSString*)password{
AppDelegate * appDelegate = [[AppDelegate alloc]init];
NSManagedObjectContext * context = [appDelegate managedObjectContext];
AppData * appData = (AppData*)[NSEntityDescription insertNewObjectForEntityForName:#"AppData" inManagedObjectContext:context];
appData.password = password;
NSError *error;
if (![context save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"AppData" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
NSLog(#"There was an error:%#",error);
}
for (AppData * adata in fetchedObjects) {
NSLog(#"Password:%#",adata.password);
}
return YES;
}
Thanks!
The right approach here is to not put this data in Core Data. If you only have one instance, there's no point in using Core Data to solve the problem. There's no benefit to using Core Data for this. Put it somewhere else. Code solutions miss the point, because even if it works, it's a bad design.
You should do like this, first create fetch request and execute a fetch. check if object exist, update data. else if no data exist create an object and save it.
If name of entity which is storing password.
Your code should look like this
AppData * appData;
NSManagedObjectContext * context = [appDelegate managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"AppData" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
if(fetchObjects.count > 0){
appData = [fetchObjects objectAtIndex:0];//assume there will be one object
// and do reset of thing
}
else{
appData = (AppData*)[NSEntityDescription insertNewObjectForEntityForName:#"AppData" inManagedObjectContext:context];
}
appData.password = password;
// save moc here
[context save:nil];

Is there a faster way to retrieve a specific managedObject in Core Data than a fetch request?

I have a method to create managed objects (IntersectionObject) each with three properties. These three properties are managed objects themselves.
PropertyObject1, PropertyObject2, and PropertyObject3 each has about 20 different possibilities.
An IntersectionObject is essentially a combination of a particular PropertyObject1, PropertyObject2, and PropertyObject3.
There are about 1200 IntersectionObjects and to create them I am using a fetch request to retrieve and set the the correct PropertyObject:
- (PropertyObject1 *)fetchedPropertyObject1FromID: (NSNumber *)propertyObjectID {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"PropertyObject1" inManagedObjectContext:[self managedObjectContext]];
[fetchRequest setEntity:entity];
NSError *error = nil;
NSArray *fetchedObjects = [[self managedObjectContext] executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
NSLog(#"error fetching PropertyObject from ID, error:%#", error);
return nil;
}
for (PropertyObject1 *object in fetchedObjects) {
if (object.propertyObjectID.longValue == propertyObjectID.longValue) {
return object;
}
}
return nil;
}
I am finding that repeating this fetch three times for each of the 1200 IntersectionObjects takes about 2 seconds, and is too slow. Is there a faster way to do this?
EDIT: the accepted answer has the solution that I used in the comments below it. It turns out simply mapping the PropertyObjects to a dictionary was the simplest and quickest way to get the associated objects.
If you have the managed object id, use objectWithID: on the MOC.
If you don't, and you're going to be creating a lot of associations in a short space of time, fetch all of the managed objects from the MOC in batches (perhaps 100 items each batch, see fetchBatchSize on NSFetchRequest), create a dictionary mapping your objectID to the managed objects so you can just do a local lookup. Turn each object back into a fault as you process the batch with refreshObject:mergeChanges:.
Use a predicate to do this, also set the fetch limit to 1. This should get your result in a much more optimized fashion:
- (PropertyObject1 *)fetchedPropertyObject1FromID: (NSNumber *)objectID {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"PropertyObject1" inManagedObjectContext:[self managedObjectContext]];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"objectID == %#", objectID]];
[fetchRequest setFetchLimit:1];
NSError *error = nil;
NSArray *fetchedObjects = [[self managedObjectContext] executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
NSLog(#"error fetching PropertyObject from ID, error:%#", error);
return nil;
}
if(fetchedObjects.count > 0)
{
[return fetchedObjects objectAtIndex:0];
}
return nil;
}

the object is not being turned into a fault

The problem randomly occurs...
Crashing Location (which is a method in NSOperationQueue)
[self.requestOperationQueue addOperationWithBlock: ^{
NSArray *titleList = [[NSMutableArray alloc] init];
NSArray *allBooks = [[CoreDataManager sharedInstance] fetchBooks];
for (Book *book in allBooks)
[titleList addObject:book.title]; // program crashed here!! failed to fault the value of book.title
}];
I use managedObjectContentChild for NSEntityDescription.
However, executeFetchRequest by managedObjectContent, which is the parent of managedObjectContentChild.
Is that the potential problem?
I tried executeFetchRequest by managedObjectContentChild, however, it leads many more different issues.
However, I am binded to use managedObjectContentChild since program is running in multiple threads by create new CoreDataManager instance for individual thread. Program will run into deadlock without using children MOC.
Thanks in advance!
CoreDataManager.m
- (id)init
{
if ((self = [super init]) != nil)
{
delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
// Writer (write data to Persistent Store Coordinator)
writerManagedObjectContext = [delegate writerManagedObjectContext];
// Parent (Fetched Result Controller)
managedObjectContext = [delegate managedObjectContext];
// Child (handling Object Context Saving for individual threads)
managedObjectContextChild = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
managedObjectContextChild.parentContext = managedObjectContext;
bookEntity = [NSEntityDescription entityForName:[Book description]
inManagedObjectContext:managedObjectContextChild];
friendEntity = [NSEntityDescription entityForName:[Friend description]
inManagedObjectContext:managedObjectContextChild];
}
return self;
}
- (NSArray *)fetchBooks
{
// Todo: fix the problem of "CoreData: error: NULL _cd_rawData but the object is not being turned into a fault"
NSArray *results = nil;
if (key == nil)
return results;
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:key ascending:ascending];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
[request setPredicate:predicate];
[request setSortDescriptors:sortDescriptors];
NSError *error = NULL;
// results = [managedObjectContextChild executeFetchRequest:request error:&error];
results = [managedObjectContext executeFetchRequest:request error:&error];
if (error != NULL)
NSLog(#"Error fetching - %#", error);
return results;
}
I tried to figure out the following discussion, but still have no clue how to do it...
CoreData: error: NULL _cd_rawData but the object is not being turned into a fault
http://www.cocoabuilder.com/archive/cocoa/311615-weird-core-data-crash.html
Here is the problem,
// use this one
results = [managedObjectContextChild executeFetchRequest:request error:&error];
// not this
// results = [managedObjectContext executeFetchRequest:request error:&error];
so I use managedObjectContextChild (child MOC) instead of managedObjectContext (parent MOC) in order to create distinct MOC for each individual threads. As a rules of concurrency of CoreData.
using managedObjectContext (parent MOC) will not cause the error of object not turning to fault and crashes the app every single time, but it's serious issue if the app happened to be using the same MOC (well, there is only one managedObjectContext in this case) at the exactly the same moment even from different threads.

Resources