I have integrated coreData in my application. I am running NSManagedObjectContext in the main thread.
-(NSArray *) getResultForContext:(NSManagedObjectContext *)context
{
NSFetchRequest *request = [[NSFetchRequest alloc]initWithEntityName:#"Person"];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Person" inManagedObjectContext:context];
[request setEntity:entity];
NSError *fetchError;
NSArray *allObjects = [self executeFetchRequest:request error:&fetchError];
return allObjects;
}
The above method is working fine but if I add propertiesToFetch to the request, executeFetchRequest return an empty array.
-(NSArray *) getResultForContext:(NSManagedObjectContext *)context
{
NSFetchRequest *request = [[NSFetchRequest alloc]initWithEntityName:#"Person"];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Person" inManagedObjectContext:context];
[request setEntity:entity];
[request setResultType:NSDictionaryResultType];
[request setReturnsDistinctResults:YES];
[request setPropertiesToFetch:#[#"firstName",#"lastName"]];
NSError *fetchError;
NSArray *allObjects = [self executeFetchRequest:request error:&fetchError];
return allObjects;
}
This method returns an empty.
What is missing here?
Assuming that (1) data is present (i.e. that there actually are some Person instances to be fetched) and that (2) the result is actually an empty array and not nil:
The only way I know to get a non-empty array in the first case but an empty array in the second case would be if the listed properties are both nil for all instances. In that case, there are no values, so the call to setReturnsDistinctResult effectively filters out every result (because nil is not considered a distinct result).
If either property is non-nil for any existing Person instance, you'll get a non-empty array. But if there are no property values to fetch, there are no distinct results, and the result is empty.
If it happens that the result is actually nil, and not an empty array, look at fetchError for clues.
The problem comes from the NSDictionaryResultType.
As said bbarnhart on this post, you must save the context to persistent store before using the resultType NSDictionaryResultType.
Be careful to the declaration of the context persistentStoreCoordinator too.
Related
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;
}
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.
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.
I am having performance issues while retrieving data from the database.
Right now, this is how I do it.
+(NSMutableArray *) searchObjectsInContext: (NSString*) entityName : (NSPredicate *) predicate : (NSString*) sortKey : (BOOL) sortAscending
{
i3EAppDelegate *appDelegate = (i3EAppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:context];
[request setEntity:entity];
[request setFetchBatchSize:5];
//[request setFetchLimit:10];
[request setReturnsObjectsAsFaults:NO];
// If a predicate was passed, pass it to the query
if(predicate != nil)
{
[request setPredicate:predicate];
}
// If a sort key was passed, use it for sorting.
if(sortKey != nil)
{
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:sortKey ascending:sortAscending];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
[sortDescriptors release];
[sortDescriptor release];
}
NSError *error;
NSMutableArray *mutableFetchResults = [[context executeFetchRequest:request error:&error] mutableCopy];
if (mutableFetchResults == nil) {
// Handle the error.
// NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
[request release];
//[context release];
appDelegate = nil;
return [mutableFetchResults autorelease];
}
Is there any other way to perform the same operation but in a faster way?
If so, it would be great if someone could help me out in this.
There are a few issues:
You do not have a predicate. Therefore you are retrieving the entire table. That is probably your single biggest issue.
You are loading all of the full objects on retrieval. This is going to slow things down considerably. If you do not need the entire objects realized then I would suggest turning -setReturnsObjectsAsFaults: back on.
The setting of -setFetchBatchSize: is probably not having the effect you are expecting. If you want to limit the number of results returned use -setFetchLimit:
Making a -mutableCopy of the NSArray results has no value. I suspect you got that from a book and recommend you stop doing it.
Ask yourself a couple of questions about your data requirements.
Do you need access to all the results at once? If you only need a small number at a time you could retrieve only the NSManagedObjectID's:
[request setResultType:NSManagedObjectIDResultType];
This will be very quick and give you a sorted list, but you'll have to retrieve the actual data as required.
If you need the data a bit faster you could retrieve faulted NSManagedObject's
[request setIncludesPropertyValues:NO];
This will allocate the objects in advance, but won't fetch the actual data until you request it.
Alternatively do you need to perform the sort as part of the fetch, or if could you somehow do it quicker once the objects are in memory?
It is worth creating yourself a test harness and trying the various options to see which are most suitable for your application.
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.