iOS: [self.fetchedResultsController performFetch:&error]; makes my app to crash - objective-c

I have a UITableViewController, and I want to feed it with the content of a core data model.
However, when I fetch the content my app crashes. This is the init method (I pass a NSManagedObjectContext to it).
- (id)initInManagedObjectContext:(NSManagedObjectContext *)context
{
self = [super initWithStyle:UITableViewStylePlain];
if (self) {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:#"Document" inManagedObjectContext:context];
request.predicate = nil;
request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"idDoc"
ascending:YES]];
/*
NSError *error = nil;
NSManagedObject *retrievedDocument = [[context executeFetchRequest:request error:&error] lastObject];
NSLog(#"retrievedDocument %#", retrievedDocument);
*/
NSFetchedResultsController *frc = [[NSFetchedResultsController alloc]
initWithFetchRequest:request
managedObjectContext:context
sectionNameKeyPath:nil
cacheName:#"CollectionCache"];
self.fetchedResultsController = frc;
[frc release];
[request release];
//HERE IT CRASHES
NSError *error;
[self.fetchedResultsController performFetch:&error];
if (error) {
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
//exit(-1); // Fail
}
}
return self;
}
I'm sure the context is correctly passed because if I uncomment the commented snippet, the stored data are correctly printed.
My guess is that something is wrong with the fetchedResultsController.
thanks

The exception was related to your wrong use of performFetch:
It returns a BOOL that tells you the success of the fetch. If you get a NO back you are allowed to check the NSError object. Otherwise you must not touch it.
Probably all the methods that use &error should be used like this:
NSError *error;
if (![self.fetchedResultsController performFetch:&error]) {
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
//exit(-1); // Fail
}

The exception was not related to fetchedResultsController but due to the not initialized NSError
NSError *error = nil;

Related

Updating only the first object in NSManagedObject

I want to update the first record in the NSManagedObject. What I have here updates all of them which I realise is because I am selecting them all and updating them all using the for but how do I just update the first record?
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Users"];
NSError *errorLoading = nil;
self.users = [context executeFetchRequest:fetchRequestNext error:& errorLoading];
for (NSManagedObject *usersObject in [self users])
{
[usersObject setValue:#"*" forKey:#"currentUser"];
}
NSError *error;
[context save:&error3];
}
From the results (array) you simply select the one at index:0
NSArray *results = [context executeFetchRequest:fetchRequestNext error:& errorLoading];
if (results.count>0) {
NSManagedObject *userObject = results[0];
[usersObject setValue:#"*" forKey:#"currentUser"];
}
NSError *saveError = nil;
[context save:&saveError];
I hope this helps
Kind of like this:
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Users"];
fetchRequest.fetchLimit = 1; //Fetch only one object (optional)
NSError *errorLoading = nil;
NSManagedObject *user = [[context executeFetchRequest:fetchRequestNext error:& errorLoading] firstObject];
if (errorLoading) {
//handle error
}
[user setValue:#"*" forKey:#"currentUser"];
NSError *error = nil;
if (![context save:&error]) {
//handle error
}
First of all, if you only need one object, you can set the fetchLimit to 1. It is not required, it's just a small optimization (it will make CoreData stop after fetching the first object). Then you execute the request just like you normally would and get the first object from the resulting array.
Another option is to use the method firstObject defined in NSArray.
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Users"];
NSError *errorLoading = nil;
NSArray *users = [context executeFetchRequest:fetchRequestNext error:& errorLoading];
NSManagedObject *singleUser = [users firstObject];
if(singleUser){
[singleUser setValue:#"*" forKey:#"currentUser"];
}
NSError *error;
if([context save:&error]){
}

controllerWillChangeContent not being called when deleting all entries of an entity

Below is the method I have implemented for a tableView "reset" button. I've verified that the entries in the Entity are being deleted, however my controllerWillChangeContent is not being called after the deletion. Is there a way to call this method manually?
CoreDataStack *coreDataStack = [[CoreDataStack alloc]init];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"FoodEntry"];
[fetchRequest setIncludesPropertyValues:NO];
NSError *error;
NSArray *fetchedObjects = [coreDataStack.managedObjectContext executeFetchRequest:fetchRequest error:&error];
for (NSManagedObject *object in fetchedObjects)
{
[coreDataStack.managedObjectContext deleteObject:object];
}
error = nil;
[coreDataStack.managedObjectContext save:&error];
EDIT : FRC CODE
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
CoreDataStack *coreDataStack = [CoreDataStack defaultStack];
NSFetchRequest *fetchRequest = [self entryListFetchRequest];
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:coreDataStack.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
Can't be certain, without knowing the internals of CoreDataStack, but it looks like you are using two different stacks: notice that your code uses:
CoreDataStack *coreDataStack = [[CoreDataStack alloc] init];
whereas the FRC uses:
CoreDataStack *coreDataStack = [CoreDataStack defaultStack];
You could change your code to use the same as the FRC (defaultStack is probably a singleton and will return the same stack whenever it is called). But if the FRC is in the same class as the delete code, it will be easier (and clearer) to just use the FRC's managedObjectContext:
NSManagedObjectContext *context = self.fetchedResultsController.managedObjectContext
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"FoodEntry"];
[fetchRequest setIncludesPropertyValues:NO];
NSError *error;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
for (NSManagedObject *object in fetchedObjects)
{
[context deleteObject:object];
}
error = nil;
[context save:&error];
Incidentally, it's always worth checking if the executeFetchRequest: and save: return an error.

Update issue with Core Data

Trying to update some Core Data. The data is actually updating "somewhere", but its not saving/updating the db.
- (IBAction)Update:(id)sender {
NSEntityDescription *entityDesc =
[NSEntityDescription entityForName:#"Preferences"
inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDesc];
NSError *error;
NSArray *objects = [context executeFetchRequest:request
error:&error];
if ([objects count] == 0) {
// No update, didnt find any entries.
} else {
for (NSManagedObject *obj in objects) {
[obj setValue:_salesPrice.text forKey:#"value"];
if(![context save:&error]){
NSLog(#"Saving changes failed: %#", error);
}
}
}
//[context save:&error];
}
I've tried [context save:&error]; in the commented area, but still no save. I also get no error on save.
You use only 1 NSManagedObjectContext? Your naming convention is not ideal. Usually you would name the entity Preference, since its one object. Try the following code.
CoreDataAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
// This is for completion. Usually you should not get the context from the App Delegate.
// Its better to pass it from the App Delegate to
// the initial view controller via a property (dependency injection).
NSFetchRequest *req = [[NSFetchRequest alloc] initWithEntityName:NSStringFromClass([Preferences class])];
NSError *error = nil;
NSArray *preferences = [context executeFetchRequest:req error:&error];
// Check error
if ([preferences count] == 0) {
// No update, didnt find any entries.
} else {
for (Preferences *preference in preferences) {
[preference setValue:_salesPrice.text forKey:#"value"];
}
}
[context save:&error];
// Check error

NSFetchRequest returning nothing

Can anyone spot why this isn't returning any ManagedObjects? I'm trying to add to the ATNSManagedObject+EasyFetching class the following, but the fetch result returns nothing. If I fetch these outside of the EasyFetch class I have 100+ objects so I know it isn't CoreData being empty.
+ (void)deleteAllObjectsInContext;
{
NSManagedObjectContext *context = [NSManagedObjectContext defaultContext];
NSEntityDescription *entity = [self entityDescriptionInContext:context];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entity];
//[request setIncludesPropertyValues:NO];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
if (error != nil)
{
//handle errors
NSLog(#"delete error");
}
for (NSManagedObject *thing in results) { <--- |results shows 0 objects|
[context deleteObject:thing];
}
NSError *saveError = nil;
[context save:&saveError];
}
Try simplifying it to:
// first get the context or pass it in as an argument (this is usually what I do for
// a deleteAll class level method like this but your call
+ (void)deleteAllObjectsInContext:(NSManagedObjectContext*)context {
NSFetchRequest* request = [NSFetchRequest fetchRequestWithEntityName:# "<yourEntity>"];
// no predicate
// no sortDescriptors
NSError* error = nil;
NSArray* results = [context executeFetchRequest:request error:&error];
if (!results || error) { // nil is an error
// handle error
}
// do something with results
}
this way you can avoid having to retrieve an NSEntityDescription object.
UPDATE:
Just wanted to add this passage:
Return Value
"An array of objects that meet the criteria specified by request fetched from the receiver and from the persistent stores associated with the receiver’s persistent store coordinator. If an error occurs, returns nil. If no objects match the criteria specified by request, returns an empty array".
One possibility is that your NSManagedObjectContext *context is actually nil.
In Objective-C, sending messages to nil is perfectly acceptable, and can make it hard to detect where an issue is.
I don't find documentation for [NSManagedObjectContext defaultContext], so I assume that is a category you wrote (or are using); and I suspect it is not always returning a valid context. Add some logging and see!
Try this method and watch the Log. It just fetches everything from the current entity.
+ (NSArray*) retrieveEntity:(NSString*) entityName {
// !!!Here you put your context
NSManagedObjectContext *context = appDelegate.managedObjectContext;
if (context == nil) {
NSLog(#"Error: No context");
return nil;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSError *error = nil;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
// Handle the error
NSLog(#"Error: No fetched objects.");
return nil;
}
else
NSLog(#"Retrieved objects count:%d", [fetchedObjects count]);
return fetchedObjects;
}
This is an example, how to call it.
// Retrieve all products
NSArray *flXProducts = [DbConnection retrieveEntity:#"FLXProduct"];
If it returns 0, then there is a problem in your database. You can reveal a problem by finding sql file of your database and tring simple SQL on it in Terminal.

Objective-C => Can't update existing objects in CoreData

I can't update objects in my database using core data, this my function :
- (void) saveItem:(NSDictionary*)dico {
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Item"
inManagedObjectContext:managedObjectContext];
Item *item =(Item *)[entity ReadSingleForKey:#"identifier"
value:[dico valueForKey:#"identifier"]
inContext:managedObjectContext];
if (!item) {
item = [[[NSManagedObject alloc] initWithEntity:entity
insertIntoManagedObjectContext:managedObjectContext] autorelease];
item.identifier = [dico valueForKey:#"identifier"];
}
item.title = [dico valueForKey:#"title"];
NSError *error = nil;
if (![managedObjectContext save:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}else{
NSLog(#"No error found.");
}
}
Even if "item" is not nil the object in the database doesn't change & I got always "No error found.".
- (NSManagedObject *) ReadSingleForKey:(NSString *) key
value:(id) value inContext:(NSManagedObjectContext *) context{
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:self];
[request setPredicate:[NSPredicate predicateWithFormat:#"%K = %#", key, value]];
[request setFetchLimit:1];
NSError *error;
NSArray *arr = [context executeFetchRequest:request error:&error];
if (arr && [arr count]) {
return [arr objectAtIndex:0];
}
return nil;
}
Any idea ??
There are several problems with your code that make it difficult to determine the error.
1) No error handling.
2) Obscure private method ReadSingleForKey - what does it return?
3) item defined as type Item and as different type NSManagedObject in same method.
Put in NSLog statements or breakpoints to examine the values of dico and item. You will soon find the place where you go wrong.
Another potential source of this error is how you read the data from the database later. For now I am assuming that this is working correctly.
The problem was in my Item class :
I was using #synthesize instead of #dynamic