controllerWillChangeContent not being called when deleting all entries of an entity - objective-c

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.

Related

I insert my CoreData in background Context and when I fetch, it doesn't show the data

I am trying to do my Core-data operation on background context.
I insert all my data on the background context but when I fetch on main thread, it doesnt show any data which I already inserted.
I dont know, where am I doing the mistake...:(
Any help appreciate.
This Is what I have tried for insert and the second one is for fetch the data.
- (void)insertNameAndSurnameInDataBase:(NSString *)name surname:(NSString *)surname numbers:(NSArray *)numbers labels:(NSArray *)labels {
AppDelegate *appDel=(AppDelegate *)[UIApplication sharedApplication].delegate;
NSManagedObjectContext *saveObjectContext = [appDel saveManagedObjectContext];
NSManagedObjectContext *bgContext = [[NSManagedObjectContext alloc]initWithConcurrencyType:NSPrivateQueueConcurrencyType];
bgContext.parentContext = saveObjectContext;
[bgContext performBlockAndWait:^{
NSError *error;
NameAndSurname *nameAndSurname = [NSEntityDescription insertNewObjectForEntityForName:#"NameAndSurname" inManagedObjectContext:bgContext];
NSString *nSurname = [NSString stringWithFormat:#"%# %#",name,surname];
if (nSurname.length == 0) {
nameAndSurname.nameSurname = [numbers objectAtIndex:0];
} else {
nameAndSurname.nameSurname = nSurname;
}
if ([bgContext save:&error]) {
} else {
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"NameAndSurname" inManagedObjectContext:bgContext];
// predicate
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"nameSurname =[c] %#", nSurname];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:predicate];
[fetchRequest setReturnsObjectsAsFaults:NO];
NSArray *fetchedObjects = [bgContext executeFetchRequest:fetchRequest error:&error];
if([fetchedObjects count] > 0){
[numbers enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
id obj2 = [labels objectAtIndex:idx];
obj = [obj stringByReplacingOccurrencesOfString:#" " withString:#""];
ContactNumber *phoneNumber = [NSEntityDescription insertNewObjectForEntityForName:#"ContactNumber" inManagedObjectContext:bgContext];
phoneNumber.number = obj;
phoneNumber.label = obj2;
phoneNumber.nameAndSurname = fetchedObjects[0];
NSError *error;
if ([bgContext save:&error]) {
} else {
}
}];
}
}];
}
- (NSArray *)fetchNameAndSurname {
AppDelegate *appDel = (AppDelegate *)[UIApplication sharedApplication].delegate;
_managedObjectContext = [appDel managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"NameAndSurname"
inManagedObjectContext:_managedObjectContext];
[fetchRequest setSortDescriptors:#[[[NSSortDescriptor alloc] initWithKey:#"nameSurname" ascending:YES selector:#selector(localizedCaseInsensitiveCompare:)]]];
[fetchRequest setEntity:entity];
[fetchRequest setReturnsObjectsAsFaults:NO];
NSError *error = nil;
NSArray *fetchedObjects = [_managedObjectContext executeFetchRequest:fetchRequest error:&error];
return [fetchedObjects valueForKey:#"nameSurname"];
}
The mistake you are making is to not test the many little pieces of this code which could be not working as expected. To troubleshoot something like this, you must start at the beginning and test one piece at a time. The first issue I see is confusion between managed object contexts…
It appears that the method saveManagedObjectContext is a getter for your main managed object context. In English, save is a verb, so that this method name implies that it is saving some managed object context. If I am correct, you should change the name saveManagedObjectContext to maybe mainMangaedObjectContext.
The statement _managedObjectContext = [appDel managedObjectContext] is quite nonstandard, assigning to an instance variable from what appears to be its getter. If, as usual, _managedObjectContext is the instance variable backing the property managedObjectContext, this statement has no effect.
In your first method you use [appDel saveManagedObjectContext], and in your second method you use [appDel managedObjectContext]. It looks like these should be the same context in order for your fetch to work. Are they?
UPDATE:
As you stated in your comment, that fixes the saving, but now you have a performance problem – saving to the persistent store on disk blocks the user interface, which is what your original code was trying to fix.
This is a solved problem. The solution is that your main-thread context, which interfaces the user, should be a child of your background-thread context which interfaces to the persistent store. It is explained nicely with code in this 2015 blog post by Marcus Zarra. For further reading, Chad Wilken has published a slight variation. Both are written in Objective-C for you :)

NSFetchedResultsController, Core data and Threading

Hello fellow overflowers
I have a hard time figuring out how to fetch NSManagedObjects in a background thread and then show the results via a NSFetchedResultsController.
This is my code so far:
_theManagedObjectContext = [[DataManager sharedInstance] mainManagedObjectContext];
__block NSMutableArray *objectsIDs;
[[[DataManager sharedInstance] backgroundManagedObjectContext] performBlock:^{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
fetchRequest.entity = [NSEntityDescription entityForName:#"Ret" inManagedObjectContext:[[DataManager sharedInstance] backgroundManagedObjectContext]];
NSArray *results = [[[DataManager sharedInstance] backgroundManagedObjectContext] executeFetchRequest:fetchRequest error:nil];
for (Ret *ret in results) {
NSManagedObjectID *moID = [ret objectID];
[objectsIDs addObject:moID];
NSLog(#"%#", objectsIDs);
}
[[[DataManager sharedInstance] mainManagedObjectContext ] performBlock:^{
[self loadDishesWithObjectIDs:objectsIDs];
}];
}];
First I fetch all the objects in a background thread and then transfering the NSMangedObjectIDs to the main thread.
In my "loadDishes" method:
- (void)loadDishesWithObjectIDs:(NSArray *)objectsIDs {
/*
[NSFetchedResultsController deleteCacheWithName:#"dishes"];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:**???**? managedObjectContext:_theManagedObjectContext sectionNameKeyPath:nil cacheName:#"dishes"];
_fetchedResultsController.delegate = self;
NSError *error = nil;
if (![_fetchedResultsController performFetch:&error]) {
NSLog(#"Fetch Failed");
}
NSArray *theDishes = _fetchedResultsController.fetchedObjects;*/
}
How would I manage to show the objects with NSFetchResultscontroller by the ObjectIDs which is fetched from the background thread ?
Thank you in advance :)
You would need to create a fetch request that looks something like this
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
fetchRequest.entity = [NSEntityDescription entityForName:#"Ret"
inManagedObjectContext:[[DataManager sharedInstance] mainManagedObjectContext]];
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"self IN %#", objectIDs];

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

Is NSFetchRequest with an NSPredicate containing a NSManagedObjectID safe to pass across thread boundaries?

If I've created an NSFetchRequest on the main thread like so:
NSManagedObject *bar = ...;
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Foo"];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"SELF.bar == %#",
[bar objectID]]];
Is it safe to pass this NSFetchRequest with an NSPredicate that contains a NSManagedObjectID to a background thread like so?
NSManagedObject *bar = nil;
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Foo"];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"SELF.bar == %#",
[bar objectID]]];
NSPersistentStoreCoordinator *persistentStoreCoordinator = ...;
[[NSBlockOperation blockOperationWithBlock:^{
NSManagedObjectContext *managedObjectContext = [NSManagedObjectContext new];
[managedObjectContext setPersistentStoreCoordinator:persistentStoreCoordinator];
NSArray *foos = [managedObjectContext executeFetchRequest:fetchRequest
error:NULL];
}] start];
I found some example code in the CoreData release notes for iOS 5 that pretty much does this, so it looks ok.
NSFetchRequest *fr = [NSFetchRequest fetchRequestWithEntityName:#"Entity"];
__block NSUInteger rCount = 0;
[context performBlockAndWait:^() {
NSError *error;
rCount = [context countForFetchRequest:fr error:&error];
if (rCount == NSNotFound) {
// Handle the error.
}
}];
NSLog(#"Retrieved %d items", (int)rCount);

Does CoreData always respect returnObjectsasFaults?

In the following code, I explicitly set returnObjectsasFaults as false. Then RIGHT after the request I check to see if objects are fault or not. NSAssert fail.
Perhaps it's because the object is an imageBlob. Perhaps I am missing something? I just want to make sure.
This is a minor issue. If I get rid the nsassert, then my programs will run anyway. Still it sucks.
+(NSFetchRequest * )fetchRequestInContext:(NSString*) entityName:(NSPredicate *) predicate:(NSString*) sortKey:(BOOL) sortAscending
{
//NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:[BGMDCRManagedObjectContextThreadHandler managedObjectContext]];
[request setEntity:entity];
if(predicate != nil)
{
[request setPredicate:predicate];
}
if(sortKey != nil)
{
NSMutableArray * sortDescriptorArray =[self getMoreSearchDescriptorsForEntity:entityName];
[request setSortDescriptors:sortDescriptorArray];
}
//request.fetchLimit = 200; //can be overridden somewhere else
request.returnsObjectsAsFaults = false;
if (entityName == BusinessString)
{
request.relationshipKeyPathsForPrefetching = arrayRelationship;
}
//[request setIncludesSubentities:<#(BOOL)#>
return request;
}
+(NSArray *) searchObjectsInContextEntityName:(NSString*) entityName Predicate:(NSPredicate *) predicate SortKEy:(NSString*) sortKey Booelan:(BOOL) sortAscending
{
NSManagedObjectContext * moc =[BGMDCRManagedObjectContextThreadHandler managedObjectContext];
NSFetchRequest *request = [self fetchRequestInContext:entityName:predicate:sortKey:sortAscending];
NSError *error;
if (entityName==BusinessString)
{
error=nil; //Some code for breakpoint
}
NSArray *fetchedObjects = [moc executeFetchRequest:request error:&error];
for (NSManagedObject * mo in fetchedObjects) {
NSAssert(!mo.isFault, #"For some reason mo is fault");
}
return fetchedObjects;
}
I've encountered the same problem while operating on a child NSManagedObjectContext. I create one as follows
NSManagedObjectContext *workerMOC = nil;
workerMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
workerMOC.parentContext = self.moc; // this is my main NSManagedObjectContext
Now after that if I do:
[workerMOC performBlock:^{
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Company"];
[fetchRequest setReturnsObjectsAsFaults:NO];
NSArray *allCompanies = [workerMOC executeFetchRequest:fetchRequest error:nil];
}];
I get faults in allCompanies. Which of course, just to clarify, does not happen if I execute the fetch request on self.moc.
However, I get appropriate pre-fetched results if I use the approach below:
[workerMOC performBlock:^{
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
fetchRequest.entity = [NSEntityDescription entityForName:#"Company" inManagedObjectContext:workerMOC];
[fetchRequest setReturnsObjectsAsFaults:NO];
NSArray *allCompanies = [workerMOC.persistentStoreCoordinator executeRequest:fetchRequest
withContext:workerMOC
error:nil];
}];
So it seems to be the case, that fetching on NSManagedObjectContexts tied directly to the NSPersistentStoreCoordinator works just fine. But in case of child NSManagedObjectContexts, which are not tied directly to the store, but rather to the parent context, do not behave as expected.
I could not find anything related to this matter in the Apple docs, but still, I don't think it's a bug.