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.
Related
I use Core Data and fetch objects into a NSMutableArray:
NSError *error;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Item" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
self.items = [[managedObjectContext executeFetchRequest:fetchRequest error:&error] mutableCopy];
I then use this mutable array to display objects in a table view. Moreover the data in that array gets changed overtime. I would like to persist that whole array in Core Data.
My initial idea was to remove all items from Core Data and by itterating over all objects in the mutable array persist them one at a time:
- (void)persistAllItemsInCoreData{
[self clearAllItemsFromCoreData];
NSManagedObjectContext *context = [self managedObjectContext];
for(int i = 0 ; i < [items count] ; i++){
Item *item = [NSEntityDescription
insertNewObjectForEntityForName:#"Item"
inManagedObjectContext:context];
item = [items objectAtIndex:i];
}
NSError *error;
if (![context save:&error]) {
NSLog(#"Could not save data: %#", [error localizedDescription]);
}
}
Howover, that doesn't work. Is there a more elegant way to persist an NSMutableArray using Core Data?
The items in the array are already in Core Data. If you change them you just need to save them. If you add an item to that array then you need to create a new NSManagedObject instance and put it into the array.
Your code implies that you do not understand the fundamentals of Core Data. I would highly recommend reviewing the documentation on this framework to get a better understanding of it.
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 have the following code to fetch a set of 'Projects' from the core data (each 'Project' has some attributes, e.g. 'title','time','endTime','pay' etc.):
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Project" inManagedObjectContext:__managedObjectContext];
[request setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"endTime" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[__managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if (mutableFetchResults == nil) {
// Handle the error.
}
[self setEventsArray:mutableFetchResults];
However only the Projects are returned not their attributes :<NSManagedObject: 0x154010> (entity: Project; id: 0x1dad70 <x-coredata://85BD9DF1-B41B-4234-8397-C2F568D0CC7A/Project/p3> ; data: <fault>
Do I have to fetch the attributes separately or have i missed something? thanks.
It's normal to see a fault with a fetch. Faults are stand ins or "ghost" objects that represent a managed object's place in the object graph without the overhead of loading a bunch of attribute data that may never be needed.
You won't see the log of attributes until you try to access one of the attributes of a particular managed object.
Newly created objects or changes to attributes of existing objects will not be saved to disk until you call save on the managed object context.
data: <fault> simply means that the attributes haven't been loaded yet. As soon as you access on the attributes, it will be retrieved from the database.
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.