How to run simultaneous searches in core-data? - ios

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.

Related

How do you call executeFetchRequest asynchronously?

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

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

Unable to replace one Entity with another in Core Data in iOS

I am working on an iOS application where I am using Core Data for storage. In my store, every entity will be unique, and I'm building a function where I replace one existing entity with another that I pass in. Here is an example of an entity that I pass:
NSManagedObjectContext *context = [[MyDB sharedInstance] managedObjectContext];
User *user = [NSEntityDescription insertNewObjectForEntityForName:#"User" inManagedObjectContext:context];
NSNumber *userNumber = 12345;
user.id = userNumber;
user.name = #"John Doe";
user.email = #"john#doe.net";
user.createdDate = [NSDate date];
[[MyDB sharedInstance] updateUser:user];
Inside my Core Data storage, I have an identical Entity already, except that the email address is "john#doe.com". My update at the moment looks like this:
-(void)updateUser:(User *)user {
NSError *error;
NSManagedObjectContext *context = [[MyDB sharedInstance] managedObjectContext];
// Create fetch request
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"User" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
// Create predicate
NSPredicate *pred = [NSPredicate predicateWithFormat:#"id == %#", user.id];
[fetchRequest setPredicate:pred];
NSArray *results = [context executeFetchRequest:fetchRequest error:&error];
if (error) {
// handle fetch error
} else {
user = [[User alloc] initWithEntity:entity insertIntoManagedObjectContext:context];
for (User *recordToDelete in results) {
[context deleteObject:recordToDelete];//record gets deleted here, which is fine
}
[context save:&error]; //this doesn't save the new entity that I passed in
if (error) {
// handle save error
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
}
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"id=%#", 12345];
[fetchRequest setPredicate:pred];
NSArray *items = [context executeFetchRequest:fetchRequest error:&error];
for (User *testObject in items) {
NSLog(#"ID: %#, Name: %#, Email: %#, Created Date: %#", [testObject id], [testObject name], [testObject email], [testObject createdDate]);
}
}
The problem is that the above function deletes the existing record in the store, however, it fails to add the new entity that replaces it. How can I correct this?
Also, i think you don't clearly understand what is NSManagedObjectContext. It's something like in-memory object cache. So, if you create object in context, it is tied to context. Object has reference to context, so passing context with object is not necessary - object's context can be obtained from it. Also, contexts and objects are not thread-safe - you cannot pass managed objects between threads and use same context in different threads. Instead, you have to: 1) Create context for every thread
2) If you need to pass something between threads, pass object.objectId from one thread, and in another thread do [context objectWithID:]. It's extremly fast, efficient and safe.
You are not calling save method on managed object context. Call save method on managedObjectContext in which you are creating new object. [managedObjectContext save:nil];
Recmonded way is. First fetch object depending on number, and delete it. After that create managed object. At the end call Save on context.

How To Format NSPredicate when doing an "IN" search with Coredata and NSFetchRequest

I have a very simple fetch request that i want to execute. One of my entities has an attribute called smartCollectionIds and it is of type transformable. I use this attribute to store an NSArray of simple strings. In my code i use an NSfetchedResultsController to populate a tableview. The predicate im using is as follows:
predicate=[NSPredicate predicateWithFormat:#"smartCollectionIds!=nil && (%# IN smartCollectionIds)",#"87F173A5-863D-4ECE-9673-A61D8F1E01FC-6285-000009A9CBAF3290"];
however this causes a crash, specifically at the pint when i perform a fetch. However, if i first use a fetch to load all my objects into an array, and then filter them out with the above predicate, the app does not crash, and i get my results as expected. So basically
THIS CODE BELOW DOES NOT WORK
-(void) tryTO
{
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Tweetary" inManagedObjectContext: [[ChubbyEyetwitterEngine sharedInstance] getManagedObjectContextForUse]];
NSPredicate *predicate;
predicate=[NSPredicate predicateWithFormat:#"smartCollectionIds!=nil && (%# IN smartCollectionIds)",#"87F173A5-863D-4ECE-9673-A61D8F1E01FC-6285-000009A9CBAF3290"];
NSSortDescriptor *secondarySortKey = [[[NSSortDescriptor alloc] initWithKey:#"created_at" ascending:FALSE] autorelease];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease] ;
[request setEntity:entity];
[request setPredicate:predicate];
[request setSortDescriptors:[NSArray arrayWithObjects:
secondarySortKey
,nil]];
[request setFetchLimit:30]; //30
NSError *error;
NSArray *results = [[[ChubbyEyetwitterEngine sharedInstance] getManagedObjectContextForUse] executeFetchRequest:request error:&error];
if (error != nil)
{
NSLog(#"Results are %d",[results count]);
}else{
NSLog(#"findAllObjectsInContext error %#",error);
}
}
BUT THIS WORKS
NSArray *tweets = [Tweetary findAllObjectsInContext:[[ChubbyEyetwitterEngine sharedInstance] getManagedObjectContextForUse]];
NSLog(#"Before filter count is %d",[tweets count]);
predicate=[NSPredicate predicateWithFormat:#"smartCollectionIds!=nil && (%# IN smartCollectionIds)",#"87F173A5-863D-4ECE-9673-A61D8F1E01FC-6285-000009A9CBAF3290"];
predicate=[NSPredicate predicateWithFormat:#"smartCollectionIds!=nil && (%# IN smartCollectionIds)",#"87F173A5-863D-4ECE-9673-A61D8F1E01FC-6285-000009A9CBAF3290"];
NSArray *bNames = [tweets filteredArrayUsingPredicate:predicate];
NSLog(#"FINAL Results %d",[bNames count]);
+ (NSArray *)findAllObjectsInContext:(NSManagedObjectContext *)context;
{
#synchronized(self){
NSEntityDescription *entity = [self entityDescriptionInContext:context];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entity];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
if (error != nil)
{
//handle errors
//NSLog(#"findAllObjectsInContext error %#",error);
}
return results;
}
}
In a nutshell, i need my fetch predicate to work when using NSfetchedResultsController instead of first loading up my objects into an array, and then applying my filter predicate. Can anyone point me in the right direction/ figure out why the predicate works only after i load my unfiltered data set into an array?
A Core Data fetch request with a predicate is translated to a SQLite query and executed
on the SQLite level. A transformable array is stored as some blob in the SQLite database,
therefore treating it as array in a fetch request does not work.
If you fetch the elements first, the blob is transformed back to an array
when the property is accessed. Therefore filtering the array of fetched objects works as expected.
I don't think there is any workaround. You cannot filter on transformable properties
in a fetch request.

Update Core Data records <fault>

I m trying to update some records in Core Data. I m adopting following steps to get it done
Fetch function with predicate retrieves the records from the Core Data
Store the result set in a Object Array
Loops through the array and update each record
Call save context
I m running into two problems
After Initial run i get < fault > in the log
I m not sure whether the save context will actually save the object
My code:
- (void)fetchExpenses {
// Define our table/entity to use
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Expense" inManagedObjectContext:managedObjectContext];
// Setup the fetch request
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
// Define how we will sort the records
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"timestamp" ascending:NO];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[request setSortDescriptors:sortDescriptors];
[sortDescriptor release];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"publishTimestamp == nil"];
[request setPredicate:predicate];
// Fetch the records and handle an error
NSError *error;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if (!mutableFetchResults) {
// Handle the error.
// This is a serious error and should advise the user to restart the application
}
// Save our fetched data to an array
[self setExpenseArray: mutableFetchResults];
[mutableFetchResults release];
[request release];
}
- (void) save: {
[self fetchExpenses];
int i = 1;
int max = [expenseArray count];
for(i=1; i<=max; i++) {
// Get the expense selected.
Expense *expense = [expenseArray objectAtIndex: i];
// Do your updates here
[expense setTimestamp:2]
}
}
The fault you are seeing in the log doesn't indicate an error but means that the managed object is not fully loaded into memory but is instead represented by a fault object. This is normal behavior. When you try to access or change an object attribute the full object will be "faulted" or read-in to memory. It's a confusing old-fashion database terminology that dates back to 1960s.
Your code does not save any objects. Changes to managed objects in memory will not be persisted until you call a save on the managed object context.
You also do not want to use a mutable copy like this:
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
… because it waste memory and can lead to duplicate managed objects. There was some code in Apple docs that got this started but its erroneous. Instead, just use:
NSArray *fetchResults=[managedObjectContext executeFetchRequest:request error:&error];
… which will return an autoreleased array of the managed objects matching the fetch.

Resources