How to improve Core Data performance?
My app has a UISearchBar allowing user to enter search keywords. Each keystroke executes a Core Data query in order to display the results as text in search bar changes.
The problem is that search bar keystrokes are lagging pretty bad... surely because of slow fetching. Any ideas how to improve the performance?
My Core Data is backed with sqlite data store which contains 1000 objects.
// searchKeyword is the string appears in UISearchBar
// Both title and author may contain several words so I can't use BEGINSWITH
NSPredicate* predicate = [NSPredicate predicateWithFormat:@"(author CONTAINS[c] %@) OR (title CONTAINS[c] %@)", searchKeyword, searchKeyword];
NSEntityDescription* entity = [NSEntityDescription entityForName:@"Book" inManagedObjectContext:managedObjectContext];
NSFetchRequest* request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
[request setPredicate:predicate];
[request setFetchLimit:10];
NSSortDescriptor* sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"title" ascending:YES];
NSArray* sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
[sortDescriptor release];
[sortDescriptors release];
execute request and fetch the results
fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:managedObjectContext
sectionNa开发者_StackOverflow社区meKeyPath:nil
cacheName:nil];
NSError* error = nil;
BOOL success = [fetchedResultsController performFetch:&error];
[request release];
Using CONTAINS slows it down. What you need to do is create a new table called searchWords (or whatever), and in that store all the words within your titles, made lower case and with accents removed. These have a relationship linking them back to the original objects. Make sure the word field is indexed.
Perform your query on this table, but instead of using CONTAINS or BEGINSWITH, do something like
word > "term" AND word < "tern"
Note that the first string in there is the search term, and the second is the search term with the last character incremented. This allows Core Data to use the SQL index to perform the search.
Apple have a Core Data WWDC session that explains this, including sample code. The sample code contains a class that handles normalising the string (I.e. Removing case), and incrementing the last character of a word in a unicode aware way.
While Amoyra's recommendation is sound, you also have a design issue.
Only the first letter typed into the search field should ever hit the disk. After the first letter you should only be refining the search results of what is already in memory.
You should filter the array that is in memory (from the first letter search) using the predicate you already have and avoid executing a fetch request as it is unnecessary.
In addition, if you are going to search on data that is already in memory (such as living in a NSFetchedResultsController
) then your entire search should only hit the objects in memory. Going out to disk is unnecessary and terribly wasteful.
While it won't speed up the query, putting the lookup into a background thread will stop the keystrokes from lagging.
You can create a trie incrementally in order to have an index for past queries (result sets are pointed by the leaf nodes). but it won't increase the performance of a single query. You can also tweak the keystroke system, don't do a fetch after each single keystroke but only after a keystroke after which a time interval (as a threshold) passed before another keystroke is recognized
When starting the app, build up a full trie, and adapt it when editing. Do not use that stupid predicate. You're only fetching a 1000 records, so that should take no time.
精彩评论