The NSFetchedResultsController monitors the changes to the whole managed object that's keeping track of. Whenever any property get modified in the current context, for instance
– controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:
gets called. This of course affect the performance of the UITableView hooked to the FRC, if the changes happen really frequently.
Is it possible to keep track then of only some properties? I need this to take advantage of the FRC for those changes that are more sporadic in time, without necessarily receiving notifications each time frequently changing attributes are modified.
No you can't.
If you need finer grain observation, just separate the properties to a different entity.
I'm going to assume that you are getting calls to this delegate method with the change type NSFetchedResultsChangeUpdate, because of changes to the underlying objects which are not relevant to how you display the data. Is this correct?
As the previous answer said, it is not possible to configure the FRC so that it ignores certain properties.
My first question would be, what exactly is the performance bottleneck? Updating will only happen for cells which are currently visible, so I'm wondering how frequently updates happen or how complex your cells are in order for this to cause performance problems?
In order to ignore changes which do not affect the way cells are displayed I would make the cells smarter. I.e. when you get the change notification and you reconfigure the cell, the cell itself could check if any relevant values have actually changed or not. If not, you can just ignore the update.
Related
Is it possible to tell the iOS system not to set some property's value to default value when the cell scrolls of the screen? It does that automatically at random times. At runtime I set the property, than the cell goes of the screen, system sets the property to default, and I can't use it anymore.. What needs to be done to avoid this behaviour?
Don't even try.
Cells are objects that are only used to display things on the screen. They are not supposed to store any permanent information, they are supposed to be used for different rows of your table at any time.
Find a different place to store your information.
As a general rule, work with the rules of iOS. If you try to work against them, you will never succeed and just waste your time.
I am displaying a number of cells, whose content is reasonably memory intensive. I have a custom controller that sets up a view to display the content, and updates the UI periodically. However, often UICollectionView asks for a particular cell several times. I want to know how to design (well) a system to re-use the controller + view if I have already created it for a given piece of data. I thought about storing a controller reference in the data object, but don't know if storing UI elements in what is meant to be data is a good idea. Also, if I did this, I would need to monitor retainCount on cell unload, and when it gets to '1', remove it from the data object, which seems a bit hacky. Similarly, a NSDictionary of data->controller pairs in the UICollectionView could also work, and would again require monitoring retainCount, or re-implementing a retain counting mechanism for my particular case. It's doable, but seems clunky.
I'm very new to ios, so it may be I'm approaching this all wrong. Any suggestions are greatly appreciated!
First of all, think if displaying a controller view inside a cell is a good idea. I'm not sure, but since i'm not familiar with your project, it's something only you can tell.
Secondly, You can create your own pool of controllers. When you need a controller to put inside the cell (in cellforIndexPath method), take one from the pool. If the pool is empty, create a new one. The cell shouldn't come with a controller of it's own, put it only in cellForItemMethod.
Is it best practice to Nil out NSFetchedResultsController Delegate when offscreen?
For example, I have a list managed by a NSFRC. When I tap a list item, I get a detail view. I can potentially change something on a detail view that will remove it from the list. Or, if I continue to slide through the detail views, I can cause it to load in more data (which would subsequently update the parent table view NSFRC).
I'm getting strange behavior when I DO nil it out. Can't seem to find advice on this anywhere.
No reason to nil out your NSFRC here. In fact, this is the main convenience of having your tableView managed by an NSFRC, is that it will update itself appropriately while you are off in other views changing data. This also applies data changes sequentially, rather than having to refetch all data when returning to your list.
I have posted a similar question time ago, looking for a technical explanation of this 'best practice'. However I didn't find anything.
I suppose that much of the suggested 'best practices' came from older iOS version, where viewDidUnload could have been called in case of memory warning, but that's not the case anymore.
In my experience, I found out that it is only mandatory to resign in case of background update and merge. All the other case depends on your application logic.
For example when you have nested CoreData entities with subsequent UIView, like so:
child1 (UITableView)-->child2 (Detail)-->child3 --> (UITableView)-->ecc...
Then, a change on child3 will have the child 1 table view react on that change, so you may want to avoid unnecessary call to a remote fetched controller, by resigning as delegate or introspecting the changement and returning YES or NO within the delegate method. It could be simpler and much efficient to refetch the query.
Keeping the same structure, suppose you have a mass update on latest UITableView by merging two context, if you have used the same context for all the controllers, then they will be trying to update its view even if not needed.
So for a simple case like UITableView-->UIView (detail) I can see no problem of keeping delegate assigned, all the other case, I would do a little analysis.
I'm making an app where when you select a cell, you're segued to a new view for reading. The cell you tapped on corresponds to an object in the Core Data store (through NSFetchedResultsController) and that object is set as the value for the article property of the view that is being segued to.
Once there, I move the position of the user in the article as they read it. (So I alter the position attribute of article via article.position = ...)
However, this occurs very frequently as they read, often hundreds of times, and each time NSFetchedResultsController is detecting the change as an update, then calling configureCell:, which then runs through a bunch of configuration for that cell. As this configureCell: method is called so often (and I only want it to be called when they go back to the table view, as that's the only time the update is needed) it's causing a decent performance loss.
I don't get why it's calling it though. I'm not saving the data with NSManagedObjectContext into the Core Data store, so why does it care? I only call that when viewWillDisappear is called, indicating that they're leaving the view, likely to go back to the table view which is where I want it!
Basically, how do I get it to only call configureCell: when it needs to/when I ask it to write the data to Core Data? It's calling it hundreds of times as is.
It doesn't matter if you're not persisting the changes to Core Data objects to disk; a change to a Core Data managed object IS a change, and so NSFetchedResultsController acknowledges it.
You could avoid updating the tableview that's not visible by setting NSFetchedResultsController's delegate to nil in viewWillDisappear:, and setting it back to self in viewDidAppear:. Also, add a performFetch: after setting the delegate.
The "hidden" NSFetchedResultsController will still be receiving all the changes made to the article object in the view that's in the foreground, but will ignore them since it doesn't have a delegate.
When going back to the articles list view, it will have a delegate again, and it will be able to react to all changes.
I have a plain (UITableViewStylePlain) UITableView with basic (UITableViewCellStyleDefault) UITableViewCells in iOS 6.1. When it enters editing mode, its cells indent as I want them to. But only if all cell labels are short: if one is long enough to be clipped on the right side, none of the table cells will indent any more.
For instance:
table with one cell: (SHORT) => indents i.e. works
table with two cells: (LONG) (SHORT) => neither cell indents i.e. does not work
What simple steps can remedy this situation? E.g., it appears as if I cannot change the preset size properties on a basic, i.e. non-custom table view cell in Xcode.
UPDATE: Here are two images that further describe the problem (1st: correct case, 2nd: incorrect case):
UPDATE: It has turned out that the root cause is not the lengths of labels. Instead it seems to be about my async. KVO handling in relation to this table view. My tableView:cellForRowAtIndexPath: calls a getter on the cell's underlying managed (Core Data) object. It seems that managed objects' default getters in turn call their own setters, probably when faulted objects are realized. Because of the way my KVO is set up, this in leads to another call of tableView:cellForRowAtIndexPath:. As it so happens, only the 2nd case involved a KVO notification and the ensuing recursive call may cause the problem (it seems slightly odd in any case) ...
I have been able to resolve this by "prefetching" the underlying managed objects in the constructor of the table view's data source. I do this by accessing the property that is displayed in the table cell. That way the first KVO notifications are triggered in a context where they cannot lead to unwanted recursive invocations of tableView:cellForRowAtIndexPath:.
If there is a better (more elegant) way to handle the situation, I'd still be interested to learn about it.