I use an NSFetchedResultsController with a stored NSFetchRequest on which I change the predicate.
After changing the predicate I call performFetch on the NSFetchedResultsController, but this does not trigger the usual controllerWillChangeContent ... controllerDidChangeContent callbacks.
So I have to reloadData on my table view to make it up to date.
Is this expected behaviour? Is this documented somewhere? Am I overlooking something?
I don't think it's documented that those methods won't be called in response to performFetch, but the documentation for when they are called does not seem to include that case. The description of performFetch() indicates that
After executing this method, you can access the receiver’s the fetched objects with the property fetchedObjects.
No mention of delegate callbacks. Meanwhile the delegate docs say that the protocol includes
...methods that will be called by the associated fetched results controller when the fetch results have changed.
Which doesn't specifically rule out changing to a different fetch request, but doesn't clearly include it either.
I'm pretty sure what you're seeing is normal. Changing the fetch request doesn't lead to delegate callbacks, but changing data affected by the existing fetch request does cause them.
Related
This is very weird situtaion of using NSFetchedResultsController with core data in iOS development by Swift.
At first, FetchedResultsController contains only info of non-changed sections but no changed section info, althought that change causes controllerDidChangeContent method call tableView.reloadData method and in turn invoke numberOfSections method.
But subsequent manual invokes, e.g. in viewWillAppear, of the FetchedResultsController results in correct showing of the changed section info along with other non-changed section info.
In other words, fetched change of an attribute referred in sectionNameKeyPath via cloudkit in a background context was saved to viewContext which in turn saved it to persistent container. During the process, controllerDidChangeContent was invoked and tableView.reloadData was called. At that moment, in numberOfSections, no changed section info can be observed in the fetchedResultsController.sections, which only contains info of non-changed sections. I have set that attribute name as first sort descriptor in the fetch request associated with the fetchedResultsController.
I can't really find any clue of the illogical; any thought is welcome.
Issue found!!! Credit giving to Michael in answering the NSFetchedResultsController calls didChangeObject delete instead of update
Cause is my predicate using string interpolation for number value. After changing the use of number predicate, magic happens. Thanks Stackoverflow community!
Question thats been bugging me here:
Are NSFetchedResultsController "controllerDidChangeContent" etc delegate methods supposed to be called when initially fetching content or only when that initially fetched content is updated / changed?
Having an issue where even though the initial fetch comes back with the results, the delegate methods are not called unless that initial results batch changes (from a network request later on for example).
This means i currently need to force a collection view update with a reloadData() since waiting for the delegate methods to call fails when there is only existing content in core data and nothing new changing it.
I have confirmed that at the time the initial fetch completes, the delegate is set and the results are valid. Any gotchas i'm missing here?
The delegate methods are only called for changes made after performFetch: has been called. You can infer this from several statements in the class reference documentation.
In my Core Dara model, there is a single Session object that holds a single Order object (though other orders can be floating around in CoreData) and Order holds Purchases. I've got an NSFetchedResultsController that fetches Purchases for the Order in the Session.
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"order.session = %#" argumentArray:#[session];
This works fine for fetching, but it doesn't call back the delegate methods for the fetched results controller in the case that the Order becomes detached from the Session. Is this just a failure in the NSFetchedResultsController, or is there a documented limitation? More importantly, how can I get around that limitation in a clean way?
To be clear, the results controller always returns the correct result after calling performFetch: it's just not firing the delegate methods.
I think this is just a "feature". The fetched results controller tracks changes to its fetched objects, but as far as it's concerned those objects are unchanged - the only change is to the related Order object. I think your best bet would be to use Key Value Observing of your Order object (or Session object) in order to be notified if they become "disconnected".
After reading lots of documentation and examples of Core Data framework, I still don't quite get it. What happens is that I sort of understand parts of a sample code or documentation, but often can't fit it into a larger picture. The second time I read the same code, I still need lots of time to figure it out just like the first time. It's so frustrating.
The NSFetchedResultsControllerDelegate vs NSFetchedResultsController is one of those concepts in Core Data that confuse me.
I think what I need is a simple and conceptual explanation, maybe analogy will be helpful.
All your core data objects have to be accessed via managed object context.
Think of NSFetchRequest as a representation of the database query. When you tell the NSManagedObjectContext (MOC) to fetch data, you give it a fetch request, so it knows what to fetch.
Now, let's say you have a table view. You make a fetch, and have all the data, even for the stuff you don't need to display. Each time the table view changes, and you need to refetch the data. There are a few other issues with just using a fetch request, though it can be done.
To solve some of these problems, enter NSFetchedResultsController (FRC). It manages the database so that you only have the objects in memory that are actually needed at the time. Furthermore, it hooks into the MOC so that when the database changes, it automatically changes its own data.
So, you create a FRC and give it a fetch request. Now, it manages the data so it only has in memory what you want. But, you need to tell it what to grab, and it need to tell you when it has data.
Thus is where the NSFetchedResultsControllerDelegate comes in.
The delegate is the glue between the table view (or some other component) and the FRC. The delegate methods are the communication channel that informs the FRC what data to get, when to get it, then hand it off to the table view.
EDIT
Yes, FRC manages the actual data part. However, when its data changes, it has to have some way to notify the guy who is watching the data. That's what the delegate is for. Let's take a different attack by looking at the delegate methods.
Imagine that the database has been changed in some way. The FRC, through its special magical incantations, has noticed the change, and, like a teenager with a new iPhone, needs to tell someone.
Specifically, again, in your case, it needs to tell the table view that is responsible for displaying the data to the user. Well, how is it going to tell the table view that the data has changed? There are actually several patterns used in iOS, and in this case, we use a delegate.
The guy who is interested in receiving this information from the FRC give him a pointer to an object that implements the delegate methods. When the FRC wants to notify the guy interested, it calls the appropriate methods on the object that is was given as the delegate.
Consider a change has happened. The FRC code would look something like this (ultra-simplified but to give the algorithmic idea).
[delegate controllerWillChangeContent:self];
// Process all the changes...
for (SectionChangeInfo *info in changedSections) {
[delegate controller:self didChangeSection:info.sectionInfo atIndex:info.index forChangeType:info.changeType];
}
for (ObjectChangeInfo *info in changedObjects) {
[delegate controller:self didChangeObject:info.object atIndexPath:info.indexPath forChangeType:info.changeType newIndexPath:index.newIndexPath];
}
[delegate controllerDidChangeContent:self];
Thus, the FRC can tell "somebody" about changes when they occur. In your case, when you give the FRC a delegate, it will call those methods, thus giving you a chance to handle the changes when they happen.
The other delegate method is called to ask the delegate what it wants to use as a section title. So, assume the FRC needs to know what to use, it will call...
NSString *sectionTitle = [[section substringToIndex:1] uppercase];
if ([delegate respondsToSelector:#selector(controller:sectionIndexTitleForSectionName:)]) {
sectionTitle = [delegate controller:self sectionIndexTitleForSectionName:section];
}
My program does work like link below:
Update results of NSFetchedResultsController without a new fetch
show result of NSFetchedResultsController to UITableView
get new object from web service and store it to core data (in same view controller, with RestKit)
update table view with notification of NSFetchedResultsController delegate
The implementation of NSFetchedResultsControllerDelegate is copied from Apple's Core Data project and my predicated is:
[NSPredicate predicateWithFormat:#"isMyTest == TRUE"]
If the property update goes from TRUE to FALSE, it removes rows from the tableview (because the object for the row is in fetchedObjects of the NSFetchedResultsController)
However, if the property update goes from FALSE to TRUE, the NSFetchedResultsController notifies nothing, so the new data cannot be seen in table view. If I update BOTH NSFetchedResulsController and UITableView manually, it shows the new data.
I thought NSFetchedResultController watches all changes in persistent store, is it too big hope? :D
(I really want to do that because other view controller can update persistent store, then it is hard to update the view controller.)
If so, can you let me know how can I update NSFetchedResultsController in beautifully way?
(update)
in reference of NSfetchedResultsController, I read words below:
A controller thus effectively has three modes of operation, determined
by whether it has a delegate and whether the cache file name is set.
No tracking: the delegate is set to nil. The controller simply
provides access to the data as it was when the fetch was executed.
Memory-only tracking: the delegate is non-nil and the file cache name
is set to nil. The controller monitors objects in its result set and
updates section and ordering information in response to relevant
changes.
Full persistent tracking: the delegate and the file cache name are
non-nil. The controller monitors objects in its result set and updates
section and ordering information in response to relevant changes. The
controller maintains a persistent cache of the results of its
computation.
"Full persistent tracking" does not mean what I want? I set up cacheName, but it works same.
I thought NSFetchedResultController watches all changes in persistent
store, is it too big hope?
The FRC only automatically observes the objects returned by its fetch. This is normally not a problem as changes to objects monitored by an FRC should arise from either the tableview UI or a background context. In the former, the FRC is alerted to the change and in the latter, merging the foreground and background context will trigger the FRC to update.
It sounds like you've got code changing values but you're not notifying the FRC that you've made any changes. (If you got a tableview displaying all objects whose isMyTest == TRUE then obviously you can't access objects from the UI whose isMyTest == FALSE.) In that case, you need to register the tableview controller for:
NSManagedObjectContextObjectsDidChangeNotification
… notifications from the context so that you can tell the FRC to update for changes made outside its observation.
I'm sorry to my mistake, I tested with new test project.
CoreData DOES full-tracking of whole persistent store.
This means, if new object that is suitable to predicate of NSFetchedResultsController, delegate will notify it.