Filter Huge NSArray

I am filtering NSArray using NSPredicate and using a filtered array for my UITableView. I use this filtering when a user enters text in a UITextField. Therefore, every time the text in the UITextField changes, I call the filter function.

It looks like this:

NSArray *hugeArray = ...; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name == %@", input]; _resultArray = [hugeArray filteredArrayUsingPredicate:predicate]; [_myTableView reloadData]; 

When I use NSArray with a lot of objects, the input becomes very slow (full input in the user interface becomes slow). Is it possible to get better performance or run a filtered command in the background?

Writing anything in a UITextField should not be blocked. When the UITableView is updated in a very short time after input, this may be normal.

+4
source share
3 answers

NSPredicate focuses on flexibility rather than speed. For NSArray internal memory (i.e. Not a master data relationship) you usually get much better performance just by using a loop.

If it is still too slow, there are several approaches:

  • Combine your requests. See Is there an easy way (on Cocoa / iOS) to queue a method call to run once in the next loop of the loop? You can create a combined trampoline so that you only update your list every few hundred milliseconds. This way, if the user types in very quickly, you will not filter the list of each individual hacker.

  • Be smarter about filtering. If you just filtered "bo" and now want to filter "bob", you know that this is a subset of this previous list. You do not need to re-filter everything. Writing a good algorithm for this requires little work, but can significantly improve performance.

  • Filter on NSOperationQueue (easier to undo than GCD, but GCD also works) and let the user interface use KVO to notify when the filtered array changes.

  • Track actual changes (adds / removes) during filtering. You should not call reloadData in your table view if you can help it. You must perform insertions and deletions ( insertRowsAtIndexPaths: . This prevents your cells from constantly bloating, and it also generally looks better. Again, the code is more complex, but improvements can be dramatic.

+12
source

If you still want to use the predicate, and if the order of the objects is not important (this means that the index of the object does not matter), you can convert the NSArray to an NSSet . Filtering a set (using NSPredicate ) is much faster than an array .

 NSSet *hugeSet = [NSSet setWithArray:hugeArray] NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name == %@", input]; NSSet *filteredSet = [hugeSet filteredSetUsingPredicate: predicate]; 

Learn more about NSSet vs NSArray vs NSDictionary in Apple Documentation

+1
source

I think running it in the background is a solution. Make a request in another queue, then reload the table in the main queue. With GCD, it will look like this ...

  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name == %@", input]; _resultArray = [hugeArray filteredArrayUsingPredicate:predicate]; dispatch_async(dispatch_get_main_queue(), ^{ [_myTableView reloadData]; }); }); 
0
source

All Articles