Core Data: Background fetch, NSFetchedResultsController and Sorting time
the problem I am encountering is the following:
I have a UITableView
which I feed with data from an NSFetchedResultsController
which retrieves around 6000 rows from core data. The fetchBatchSize
of the NSFetchRequest
is set to 20 and if I do not apply any NSSortDescriptor
the fetch is fast enough to not block the UI thread.
However, I do need to display those rows sorted alphabetically for which I use the following NSSortDescriptor:
[[[NSSortDescriptor alloc] initWithKey:@"optionText" ascending:YES selector:@selector(localizedCaseInsensitiveCompare:)] autorelease];
And here is when things change, the fetch operation now takes around 3 seconds to complete because the 6000 rows are being sorted. Obviously during those seconds the UI is blocked and the user experience is terrible.
I know that I could do the fetch in a background thread and then pass to object IDs to the main thread, but in that case how could I still use the NSFetchedResultsController
in the main thread (I am also using it to observe changes on the data)?
I have also indexed
the attribute on w开发者_Python百科hich I am sorting but that only optimises look-ups and not sorting performance.
Any ideas would be much appreciated, thanks!
How about using NSFetchRequest's batchSize
property?
If you set a non-zero batch size, the collection of objects returned when the fetch is executed is broken into batches. When the fetch is executed, the entire request is evaluated and the identities of all matching objects recorded, but no more than batchSize objects’ data will be fetched from the persistent store at a time. The array returned from executing the request will be a proxy object that transparently faults batches on demand. (In database terms, this is an in-memory cursor.)
I do a background batch imports in an NSOperation which uses a separate NSManagedObjectContext. Periodically I save the second context, which fires a notification to update my main NSManagedContext which my NSFetchedResultsController is hooked up to.
Maybe a similar technique could be applied to your fetch
Here is a cocoa is my girlfriend article about it:
http://www.cimgf.com/2011/05/04/core-data-and-threads-without-the-headache/
and the technique is also mentioned in the core data programming guide 'importing in batches'
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreData/Articles/cdImporting.html#//apple_ref/doc/uid/TP40003174-SW1
First of all, NSFetchedResultsController is typically used on main thread. And It does not support background fetch till now Apple release iOS 6.
So when you invoke the performFectch of NSFetchedResultController, you must "block" the main thread for a while. However we do want the time to be minimal.
(As far as I could remember, you must set a sort descriptor to NSFetchedResultController. So I am not sure how did you make it work while not set a sort descriptor. have a look at the class reference)
I am not sure if you are using Sqlite Store. If so, I can hardly believe your sort descriptor work. (have a look at Core Data Programming Guide: Trouble shooting section). If not,keep so many datas in memory would not be a good idea
Finally we reached the point why it's slow. This sort using "localizedCaseInsensitiveCompare:" makes your fetch slow as compare Unicode string would be slow. (mentioned in WWDC 2010 Core Data Performance on iPhone).
As many of other application, you should create a non-Unicode string field/property based on your "optionText" and sort based on that non-Unicode string property.
Using a cache would probably help to get a better performance.
I have got the same problem and realized that in the first call the fetch needs more than 3 seconds but performing the fetch twice it shows immediately its results.
Displaying 6000 rows in one table might not be the best solution in terms of user experience. Maybe you should add a filter table before. Similar to the groups in the address book. This would maybe make for a better user experience if you manage to reduce the number of rows per filter option to a more manageable number. This would reduce load time and time to scroll.
I do not know what kind of data you are displaying, so maybe there is no way but to display all in one long list. For people you could add options for gender and age-groups. For cars you could add a filter by brand and models....
Have you tried running the performFetch: method in the background, either with
[controller performSelectorInBackground:@selector(performFetch) withObject:nil];
or
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[controller performFetch];
});
精彩评论