Performance of NSCoreData entries fetching - ios

For example, i have 10 000 entries in database, and i need to display them into a UITableView.
So, i should setup all the NSCoreData stuff, create NSFetchRequest, and NSFetchedResultsController.
Then i could access these entries in cellForRowAtIndexPath with [NSFetchedResultsController objectAtIndex] method.
The questions is:
Will NSFetchedResultsController load all these objects in a LAZY way, only after user actually SCROLL UITableView to a corresponding cells?
Is it enough just to set NSFetchRequest's fetchBatchSize to a number equal to cell count on screen?
Should i use separate NSManagedObjectContext with background thread for loading these objects? In case of separate thread, how will work [NSFetchedResultsController objectAtIndex] method, while it called from Main-UI-thread?
Should i even worry about these things, while i have just 10k entries?

The NSFetchedResultsController takes care of all your concerns for you.
Lazy Loading? Indeed, the FRC will try to be as lazy as possible. Nothing for you to do. Actually, it is optimized for scrolling, speed, performance, memory usage, you name it.
Batch Size? Irrelevant. You can just forget about it. If you feel like it, set up a performance test and compare with and without batchSize. I predict you will not find any difference or it will be negligible. (If anything at all maybe with extreme scrolling speeds.)
Separate NSManagedObjectContext? Absolutely no. The NSFetchedResultsController is supposed to be used on the main thread.
Concern with number of records? You do not have to worry about this at all for the above reasons, especially lazy loading. I have had great results with X00.000s records.

If you set the batch size correctly.
I go for about twice as much
Don't bother, just run it off the main UI moc
Try it and see. CD can handle that volume, but there are still performance optimisations that you can do tha depends on things that aren't in your question.

Related

Do you put NSFetchRequest into cellForRowAtIndexPath?

It seems me somewhat slow to perform NSFetchRequest in cellforRowAtIndexPath.
How do you treat it? Is it more memory / time efficient to perform it in viewDidLoad, cache result into a dictionary, and use that in cellForRowAtIndexPath?
It is a bad idea due to performance reasons, as #Paulw11 mentioned in his comment. Additionaly, you will execute fetch requests more times than it's actually needed (while scrolling the table view back and forth), because cellForRowAtIndexPath is called each time a cell is reused.
I would recommend using NSFetchedResultsController. It is designed specifically to show Core Data records in UITableView. It allows batch fetching (doesn't fetch all the objects, but only the ones that need to be displayed). Also you will be able to easily track changes in your model.

Memory Management & Performance with EK EventKit

In a calendar app I display events based on the EventKit API. I fetch events from EKEventStore and display them in daily, weekly, monthly views, as lists, etc. Now I am running into some performance problems on iPhone 4.
The performance problems are mainly speed related. It takes several seconds, for example, to collapse or expand all table view sections (representing dates) to show the rows (representing events). It also takes 5-8 seconds to reload the table for the editing / export interface. I would have to check Instruments to give more details.
So far there have been no memory issues.
My strategy right now is to minimise the memory footprint. I am using arrays in memory, but they only contain the eventIdentifier, a short string. I can retrieve events with the EKEventStore method eventWithEventIdentifier:. I suspect that this is the reason for the performance hit.
Two alternatives come to mind:
Use EKEvent objects instead of identifiers. However, I believe that this can be unpredictable regarding memory. Some events have lots of text so that the amount of data to be kept in memory is not limited. The duration of the period that has to show events could potentially also be very long.
Port everything to Core Data, maybe with original EKEvent objects stored as transformable objects. This would be a major refactoring, but I could take advantage of NSFetchedResultsController and its optimisation features.
I have tried 1 and 2 - performance is still bad!
What is your experience? Have you seen performance issues with repeated calls to the EKEventStore database? What would be your advice?
UPDATE:
Instruments report that indeed the tableView's reloadData takes quite long (1.5 secs). I am not sure why because the state of the table view (collapsed sections or not) and the entire data are loaded before and that code is efficient.
I am not calculating any cell heights (sometimes this has been reported to force the entire table to load before display). The same lag appears when I call
[tableView beginUpdates];
[tableView endUpdates];
in order to animate the collapsing of the sections.
Note: maybe the topic of this question should be changed eliminating the EventKit part.
While I have never used EventKit, I can give you some suggestions:
NSFetchedResultsController is a great thing, especially when your data is going to be changing after your table view has initially been loaded. The NSFetchedResultsController monitors for changes for your data and the NSFetchedResultsControllerDelegate protocol allows for incremental changes to your UITableView rather than reloading an entire UITableView when changes are made to the data that populates your table. In general, avoid doing a full reloadData on your UITableView when possible.
If drawing your UITableView is really slow, chances are the source of the issue lays somewhere in the tableView:cellForRowAtIndexPath: method (almost guaranteed, although other culprits could be other UITableViewDataSource methods such as tableView:titleForHeaderInSection: or
tableView:titleForFooterInSection:). If you are reading from disk, not using cached UITableViewCells, or doing heavy drawing in these methods, it could cause your UITableView to be extremely slow to reload / scroll.
If you find that you are reading from the disk or performing some other slow operation in tableView:cellForRowAtIndexPath:, consider performing one read from the disk before your UITableView is drawn (i.e. in viewDidLoad:) and caching the results in an NSArray. Then your UITableViewDataSource methods can just read from the NSArray instead of the disk.
The problem is your row animations and (probably) recalculating sections. This is pretty common, and is a UITableView thing rather than an EventKit or CoreData thing. More profiling should give you some ideas of how you can optimize your sections to be more performant.
Look at your implementation of the UITableViewDataSource methods that involve sections:
- numberOfSectionsInTableView:
- tableView:numberOfRowsInSection:
– tableView:sectionForSectionIndexTitle:atIndex:
– tableView:titleForHeaderInSection:
– tableView:titleForFooterInSection:
You are probably doing things in those methods that are relatively heavy. Maybe to build your sections you have to look at all of your data (this is common). If that's the case, come up with a reasonable strategy for caching the section information and invalidating it when the event store changes.

How to effectively deal with large datasets in Core Data?

I am using core data in my app to store entities that could have as many as 50k objects or more. I have this paired to an NSFetchedResultsController in a table view. The table view works fine due to cell reuse however my biggest problem is queuring the actual database to get the dataset.
When i first load the table view i need all results from the db. I am using the default fetch request with a single sort descriptor and I have set the batchSize to 1,000. On an iPad 2 this query takes up to 15 secs to finish! I also have to run this query after a search has been cancelled so overall it makes the app unusable. My assumption is that CD still has to resolve all those results or setup the sections or something, i really have no idea but just using the batchSize doesn't help?? The content is also very dynamic in the sense that new rows are always getting added, sort order changing etc.. so caching has a limited benefit.
I am thinking now that the best option would be to use a fetchLimit in the fetchRequest and then implement some basic paging. When the table view scrolls to the end fetch the next "page" of results? My only problem with this approach is that i lose the sectionIndex and i cant think of any way around that.
Anyone have any ideas or dealt with this issue already?
When you set the fetch request for the FRC the batch size should be just a few items bigger than, maybe twice the size as, the number of items that can be seen on screen at any one time. The FRC already does the pagination for you you just need to set the page size better.
s.newave,
Do your rows have variable height? If so, then the table view asks you to calculate each height and that causes every row to be fetched. 15 seconds is not an unreasonable time to fetch 50K items.
The bigger problem is your statement about not wanting to change your design. Frankly, a 50K item tableview is useless. You should change your design -- not because CD is slow, it isn't -- but because your design is not pragmatically usable.
Andrew
P.S. The fetched results controller is designed for mainstream applications. a 50K table view is not a mainstream app. If you insist on keeping with a 50K table view design, you will have to make your own controller.

NSFetchedResultsController - track changes only to a subset of the properties

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.

List of Items vs. Data Source

When building a view for an iPhone app, one must consider how variable data will be determined by the view. Two design options jump readily to mind:
An NSArray of items
A dataSource property, which implements a protocol and returns the items.
The former is used by views such as UITabBar, while the latter is used by UITableView. What are the pros and cons of these options? Is there a reason for the two distinct paradigms, or is one universally superior?
It's mostly about the amount of data and limited amount of memory in relation to simplicity.
Simpler is always better if you can get away with it. A tab bar probably have less than 10 items which is no problem to hold in memory at once so the simplest solution is the best.
A table view however may have thousands of rows that may contain expensive data like images. Therefor it has a more complex design to be able to keep only the necessary data in memory.

Resources