I am using Core Data in an IOS application and am experiencing a bottleneck while fetching data. I am calling executeFetchRequests multiple times in a loop to fetch 1 result each time. Each fetch takes a short time, but since I am calling it about 500 times the fetch takes at least a second. I am having trouble calling executeFetchRequest using GCD.
My code looks like this. (I removed code that saves the data, since it is not the problem).
Setup Code (I am unsure if this should go inside the threaded code, it doesn't work either way).
NSManagedObjectContext *context = [self managedObjectContext];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Entity"
inManagedObjectContext:context];
NSFetchRequest *fetchrequest = [[NSFetchRequest alloc]init];
[fetchrequest setEntity:entity];
Setup GCD stuff
dispatch_group_t x_group = dispatch_group_create();
dispatch_queue_t x_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
Iterate through each predicate
for (NSPredicate *predicate in arrayOfPredicates) {
[fetchrequest setPredicate:predicate];
dispatch_group_async(x_group, x_queue, ^{
NSError *error;
NSArray *array = [context executeFetchRequest:fetchrequest error:&error];
for (Entity *managedObject in array) {
// save stuff to array inside of thread to pass to an array using locks.
}
});
}
dispatch_group_wait(x_group, DISPATCH_TIME_FOREVER);
... more code here...
However this code never gets past dispatch_group_wait and when fetching using this method the fetched managedObject is always an empty string. How can I do this asynchronously, so that there isn't a long delay period?
The short answer is a context is not thread safe. See: Concurrency with Core Data
The longer answer is you may have the wrong approach. I would setup the code so all required objects can be fetched using a single request: even if I needed to change the schema to make that happen.
Update Example of Single Predicate
NSPredicate *predicate = [NSCompoundPredicate orPredicateWithSubpredicates:arrayOfPredicates];
[fetchrequest setPredicate:predicate];
Related
When I was in use coredata multiple entities at the same time the request data will cause this phenomenon?
I couldn't find the reason, want to ask whether this reason
The following method is one of my program, there are two similar methods, they may at the same time, the cause of the deadlock is this?
+ (NSArray*)getChat{
NSManagedObjectContext * managedObjectContext = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSError * error = nil;
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Chat" inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc] initWithKey:#"lastMessage" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor1, nil];
[request setSortDescriptors:sortDescriptors];
NSArray * FetchResults = [[managedObjectContext executeFetchRequest:request error:&error] copy];
return FetchResults;
}
Managed objects themselves are not thread safe. If multiple threads access the NSManagedObjectContext this will cause a deadlock. Apple recommends on "Concurrency with Core Data" to use the thread confinement pattern for work with concurrency in Core Data. Depending on your app, you can try:
Create a separate managed object context for each thread and share a single persistent store coordinator.
Or create a separate managed object context and persistent store coordinator for each thread.
And with multiple contexts you can pass objects between contexts by passing their objectIDs and fetching them in the other context.
You may want a more sophisticated approach using Nested Contexts. Here is a good article to help you with: Multi-Context CoreData.
I have 90 CoreData entities called "ItemModel" with 2 attributes 'uid', 'description', where each of the item is inserted as an NSManagedObject:
NSManagedObject *object = [NSEntityDescription insertNewObjectForEntityForName: #"ItemModel" inManagedObjectContext: AFYDelegate.managedObjectContext];
The first server call assigns the 'uid' to each of the 90 items fetched above for key "uid". The context is not saved here.
On a later second server call, I like to update 'description' for the 90 items, for each of the NSManagedObject using indexPath - by fetching and passing each object to the following method and saving the context:
[self updateItemToDataModel:object withData: description];
....
....
- (void)updateItemToDataModel:(NSManagedObject *) object withData:(NSString *)data
{
[object setValue:data forKey:#"description"];
NSError * error = nil;
if (![self.managedObjectContext save:&error]) {
//Handle any error with the saving of the context
NSLog(#"%#",error.localizedDescription);
}
}
The above works fine in updating CoreData BUT after closing the Simulator and running the code again, there will be two duplicates for each item with the same 'uid' and 'description'. This means I have 180 items now. Repeatedly closing and running the code creates more and more items.
I tried removing updateItemToDataModel method, resetting the Simulator and it works fine with 90 items.
I'm new to CoreData if someone can help. What's wrong with my code if I only wished to update existing items?
You are inserting a new object into the MOC (managed object context) each time--instead of doing a fetch and finding an existing instance of the object you wish to update.
To fetch the existing object you might execute a fetch request like so...
NSPredicate * predicate = [NSPredicate predicateWithFormat:#"uid == %#", uidToMatch];
NSFetchRequest * fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setPredicate:predicate];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"ItemModel" inManagedObjectContext:managedObjectContext]];
NSError * error = nil;
NSArray * results = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
if ([results count]) {
// you may need to handle more than one match in your code...
// you could also set a fetch limit of 1 and guarantee you only get the first object, eg: [fetchRequest setFetchLimit:1];
}
else {
// no results
}
You might want to wrap that in a helper function so you can re-use it. And read up on NSFetchRequest, NSPredicate and writing predicates in order to do fancier fetch requests.
Good evening,
I need some help with Core Data. I have two entities, Images & Album (ImageGroup in code), that have a many-to-many relationship. Each image can have many Albums and an Album can have many images.
In the application, the user can create an Album. Once the album is created, all the new pictures get added to that album. This is working fine.
But now I have a question regarding the fetch request in Core Data. Please have a look at my current "fetch" code:
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSError *error;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"ImageGroup" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
[fetchRequest release];
// Load all the other albums
for (ImageGroup *group in fetchedObjects) {
NSString *albumName = group.name;
NSSet *groupSet = group.group;
for (Images *image in groupSet) {
NSLog(#"imageTN: %#", image.nameTN);
}
}
This is working fine as well. However, during the "for in" loop for groupSet, a select query is executed for EACH step in the loop. This is fine if you only have a few images, however, if you have 1000+ images stored, this code would execute 1000+ queries.
Is there a way to load all the images that are associated with the Album in one single query?
(SELECT * from Images WHERE album_ID = ? something like that).
This is the first time i'm using Core Data and not sqlite. Any help is truly appreciated.
This is possible, if you don't go through the set, or you prefetch the images in group (since your current fetch request fetch ALL ImageGroup entities, you probably should avoid that).
To prefetch the to-many relationship add to your request:
[fetchRequest setRelationshipsForPrefetching:#[#"group"]]
i would also add:
[fetchRequest setIncludesPropertyValues:YES] and:
[fetchRequest setReturnsObjectsAsFaults:NO]
If you want to issue a single request per ImageGroup you could use:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Images"];
[fetchRequest setIncludesPropertyValues:YES]
[fetchRequest setReturnsObjectsAsFaults:NO]
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"%# IN <albom set>",group.objectID]; //not tested
note: by the end of the external loop you have faulted (allocated in memory) all alboms and all their attached images.
You should fault each ImageGroup you are done with to keep memory peeks at bay.
I am trying to understand how to run simultaneous searches in core-data.
Here is my example, but it doesn't work, because one of the GCDs seems to never activate
If I leave the custon MOC in there I get an error "unable to find model for entity 'Recipe''
-(void)performSearch:(NSString*)name{
//TODO: Is there a better way
//In case the previous search hasn't finished
if (globaDispatchRequestInprogress) {
//Send on GCD
dispatch_queue_t searchQueque = dispatch_queue_create("search queque 2", NULL);
dispatch_async(searchQueque, ^{
NSLog(#"\n\nDispatch 2 In Progress*******\n\n");
//Init local variables
NSFetchRequest *fetchRequest =[[NSFetchRequest alloc]init];
NSError *error;
//Create own MOC for multiThread
NSManagedObjectContext *tempContext = [[NSManagedObjectContext alloc]init];
[tempContext setPersistentStoreCoordinator:[self persistentStoreCoordinator]];
NSPredicate *recipeName = [NSPredicate predicateWithFormat:#"ANY recipe.name ==[c] %#",name];
//Set predicate to fetch request
[fetchRequest setPredicate:recipeName];
//Set query. We are searching recipes
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Recipe" inManagedObjectContext:tempContext];
//sets up fetch request details
[fetchRequest setEntity:entity];
//Attempt to speed up program
[fetchRequest setReturnsObjectsAsFaults:NO];
//Perform fetch assign to return array
NSArray*records = [tempContext executeFetchRequest:fetchRequest error:&error];
//Add to temporary results
//TODO: Add to NSDictionary
[self addResultsToTemporaryResults:records];
NSLog(#"Total results = %i",[_temporaryResultsArray count]);
NSLog(#"\n\nDispatch 2 END**************\n\n");
});
}
//Send on GCD
dispatch_queue_t searchQueque = dispatch_queue_create("search queque", NULL);
dispatch_async(searchQueque, ^{
NSLog(#"\n\nDispatch In Progress*******\n\n");
//Set flag
globaDispatchRequestInprogress=YES;
//Init local variables
NSFetchRequest *fetchRequest =[[NSFetchRequest alloc]init];
NSError *error;
//Create own MOC for multiThread
NSManagedObjectContext *tempContext = [[NSManagedObjectContext alloc]init];
[tempContext setPersistentStoreCoordinator:[self persistentStoreCoordinator]];
NSPredicate *recipeName = [NSPredicate predicateWithFormat:#"ANY recipe.name ==[c] %#",name];
//Set predicate to fetch request
[fetchRequest setPredicate:recipeName];
//Set query. We are searching recipes
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Recipe" inManagedObjectContext:tempContext];
//sets up fetch request details
[fetchRequest setEntity:entity];
//Attempt to speed up program
[fetchRequest setReturnsObjectsAsFaults:NO];
//Perform fetch assign to return array
NSArray*records = [tempContext executeFetchRequest:fetchRequest error:&error];
//Add to temporary results
//TODO: Add to NSDictionary
[self addResultsToTemporaryResults:records];
NSLog(#"Total results = %i",[_temporaryResultsArray count]);
globaDispatchRequestInprogress=NO;
NSLog(#"\n\nDispatch END**************\n\n");
});
}
I see several things that make me suspicious, but no obvious smoking gun.
If you're seeing "unable to find model", that suggests that your persistent store coordinator is not being configured the way you think it is. It would be interesting to NSLog self.persistentStoreCoordinator.managedObjectModel, and also self.persistentStoreCoordinator.managedObjectModel.entitiesByName.
The preferred GCD approach to Core Data is to use performBlock: or performBlockAndWait:, with the proper concurrency type for your managed object context. See http://developer.apple.com/library/mac/#releasenotes/DataManagement/RN-CoreData/index.html
You're keeping the results of your fetch around, in your addResultsToTemporaryResults: call. We don't see the source for it, but is it thread-safe? Those records you found have no existence outside of the tempContext you fetched them in, and may only be accessed from the thread that found them. You probably want to be using NSManagedObjectIDs there (and perhaps you already are).
Your second call to dispatch_queue_create() will always be executed. Did you mean to do an if-else instead of a simple if?
When you do -executeFetchRequest:error:, check the result. If it's a nil result, take a look at the NSError you passed in.
My question, I believe, is fairly simple but I can't seem to find a decent and elegant solution to it. Maybe Core Data doesn't have an elegant solution or my Google skills have let me down, in which case I apologize.
Lets say I have a simple messaging application. I have two Core Data entities: Thread and Message. A Thread contains many Messages.
My application should open with a UITableView in which all the Threads are listed. The application will use RestKit and its object mapping mechanisme to load the data. The Messages have a created property.
So I want to sort my list of Threads so that the Thread with the latest Messages appears on top and the thread with the oldest messages in it will apear on bottom.
I believe this is also what happens in Apple's own messaging application, and it uses Core Data as well. This is the main reason I hope that there is a more elegant solution than for example store an extra to one relation to the latest message in the database. Which is in my case unpractical because I use RestKit object mapping and don't have influence on the HTTP API.
I've tried transient values, this doesn't work because you can't sort on them. I have tried a sort descriptor with a NSComparisonResult block, but this also doesn't work because core data says it can't sort on to-many relationships. And I've looked into fetched properties, but I can't figure out what predicate to use.
Thanks in advance.
You can create a readonly lastMessageDate property for your Thread class. Implement your own getter to return the newest date from threads messages. Then sort your threads using this property:
NSArray *sortedThreads = [threads sortedArrayUsingComparator: ^(Thread *thread1, Thread *thread2) {
return [thread2.lastMessageDate compare:thread1.lastMessageDate];
}];
Did you try NSSortDescriptor while fetching the Core Data Object like:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:YOUR_ENTITY inManagedObjectContext:[AppDelegate objectModel]];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"createdOn" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSError *error;
NSMutableArray *mutableFetchResults = [[[AppDelegate objectModel] executeFetchRequest:fetchRequest error:&error] mutableCopy];
if (!mutableFetchResults) {
// Handle the error.
NSLog(#"Fetching data failed: %#", error);
}
return mutableFetchResults;