I'm trying to figure out the best way to perform a fast search using a UISearchDisplayController.
I have a plist file with more than 36000 entries. I load this file in a dictionary and I perform the search in this dictionary. It works but it's kinda slow and there is lag between each touch event. I want an autocompletion effect so I need the search to be triggered for each touch event.
I tried using thread to perform the search in background with the following code :
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
[NSThread detachNewThreadSelector:#selector(filter:) toTarget:self withObject:searchString];
return NO;
}
// Filter function looks like this
-(void) filter:(NSString *)search {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self.filteredList removeAllObjects]; // empty array of results
for (NSString *s in self.keys ) {
NSComparisonResult result = [s compare:search options:(NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch) range:NSMakeRange(0, [search length])];
if (result == NSOrderedSame) {
[self. filteredList addObject:s ];
}
}
[ self.searchDisplayController.searchResultsTableView reloadData];
[pool release];
}
But my application crashes randomly with the following message:
Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[NSCFArray objectAtIndex:]: index (1) beyond bounds (0).
I'm sure it's because I dont use threading properly.
I also tried using [self performSelector:#selector(filter:) withObject:searchString afterDelay:0.5]; but I'm also facing application crashes.
What is the best way to handle this ? I'm not really good with threads but I think it's the best way to go, isn't it ? I also tried a solution with SQLite but the application is still not so responsive.
My data are actually zip codes and cities (36000 unique different cities but 6500 unique zip codes since multiple cities can have the same zip code). I want my search item to be either a zip code or a city name. I know that one big dictionary is definitely not the best structure. How could I organize my data for more efficiency?
Thank you for helping me with this.
The problem is that your search string is longer than one of your original strings in the array. When comparing from 0 to [search length] you are falling outside of s. You should first make sure that s is longer than search:
for (NSString *s in self.keys ) {
if ([s length]>=[search length]) {
NSComparisonResult result = [s compare:search options:(NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch) range:NSMakeRange(0, [search length])];
if (result == NSOrderedSame) {
[self. filteredList addObject:s ];
}
}
}
Related
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
[displayItems removeAllObjects]; //clear array to ensure no repeat info
if ([searchText length] == 0) {
displayItems = (NSMutableArray *)allItems;
}
else {
//search by item category
NSPredicate *catPredicate = [NSPredicate predicateWithFormat:#"category
CONTAINS[cd] %#",searchText];
[searchable filterUsingPredicate:catPredicate];
//further search by item name
NSPredicate *namePredicate = [NSPredicate predicateWithFormat:#"name CONTAINS[cd]
%#",searchText];
[searchable filterUsingPredicate:namePredicate];
displayItems = searchable;
searchable = (NSMutableArray *)allItems;
}
[self.searchResults reloadData];
}
This method is part of a simple searchable table view I am trying to create for a larger project. My code compiles and runs, and when i type something into the search bar the search function appears to work, but then the program crashes as soon as a second letter is typed. If I type two letters in a row, it throws 'NSInvalidArgumentException', reason: '-[_NSArrayI filterUsingPredicate:]: unrecognized selector sent to instance 0x6d6c040', but if I type one letter and then hit enter or backspace, it throws this guy 'NSInvalidArgumentException', reason: '-[_NSArrayI removeAllObjects]: unrecognized selector sent to instance 0x6a7f300' when I type a second letter.
I am pretty new to objective-c, and this has me perplexed. Any help I could get would be greatly appreciated.... :-/ Still having issues since update.
"One does not simply cast NSArray into NSMutableArray and then call NSMutableArray methods on it" - Boromir
Create a mutable copy instead, like this:
searchable = [allItems mutableCopy];
NOTE: Make sure to release searchable when you are finished with it.
You have to use NSMutableArray to call the methods.
NSArray has a method "filteredArrayusingPredicate".
The simple solution is use NSMutableArray.
I am having a problem in executing the following code while searching in the table. This code works fine elsewhere. But currently it is giving an error as
[_UITableViewSeparatorView rangeOfString:]: unrecognized selector sent to instance 0x6041790
Following is the code that is troubling me. Please let me know the bug gidden in there.
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
[tableData removeAllObjects];// remove all data that belongs to previous search
if([searchText isEqualToString:#""] || searchText==nil)
{
[displayTable reloadData];
return;
}
NSInteger counter = 0;
for(NSString *name in dataSource)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
NSRange r = [name rangeOfString:searchText];
if(r.location != NSNotFound)
{
if(r.location== 0)//that is we are checking only the start of the names.
{
[tableData addObject:name];
}
}
counter++;
[pool release];
}
[displayTable reloadData]; }
Thanks in advance!!
Looking forward to your responses.
thanks
It looks like you're over-releasing the strings that you have stored in dataSource. I would check any place that you use/create those strings to make sure that you aren't releasing them more times than you should.
It means that the memory where the string should reside in memory was freed and there is another object on that place (_UITableViewSeparatorView in your case). Make sure that you are not over-releasing the string in array
You can try to search with NSZombiesEnabled in instruments: link
What are you putting in dataSource? Evidently, it contains an object that is not an NSString.
i've got huge problem. I've copied some code from table search sample from Apple Resource pages.
here's the case:
#pragma mark -
#pragma mark Content Filtering
- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope
{
[self.chatMessagesArrayCopyForSearching removeAllObjects]; // First clear the filtered array.
if ([searchText length]==0)
{
}else
{
for (FriendMessage *friend in chatMessagesArray)
{
NSComparisonResult result = [friend.message compare:searchText options:(NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch) range:NSMakeRange(0, [searchText length])];
if (result == NSOrderedSame)
{
[self.chatMessagesArrayCopyForSearching addObject:friend];
NSLog(#"%#", friend.message);
}
}
}
}
application crash when for example i type one letter, and then the second letter. probably there something with friend.message becouse console says:
-[AccessibilityObjectWrapper message]: unrecognized selector sent to instance 0x5d8d580
FriendMessage is custom class, inherited from NSObject, and message is standard NSString *.
thanks for any provided help
mapedd
p.s. sorry if code isn't very readable
The fact that it says 'AccessibilityObjectWrapper' in your error tells you that there might have been a FriendMessage object there at some point but it's gone now :)
This is typically because there is a missing retain somewhere in your code.
Where do you create the array of FriendMessage objects - can you edit your question and add that code as well?
Thanks.
NSArray *results = [managedObjectContext executeFetchRequest:request error:&error];
if(results == nil){
NSLog(#"No results found");
searchObj = nil;
}else{
if ([[[results objectAtIndex:0] name] caseInsensitiveCompare:passdTextToSearchFor] == 0) {
NSLog(#"results %#", [[results objectAtIndex:0] name]);
searchObj = [results objectAtIndex:0];
}else {
NSLog(#"No results found");
searchObj = nil;
}
}
The code above compares data a user enters to data pulled from a database. If I enter data which is in the database; it works. But if I enter complete gibberish it returns the error below instead of "No results found."
*** WebKit discarded an uncaught exception in the webView:shouldInsertText:replacingDOMRange:givenAction: delegate: <NSRangeException> *** -[NSCFArray objectAtIndex:]: index (0) beyond bounds (0)
The results array being null should be accounted for during the checks in the above code, no?
You are potentially throwing an exception on the following line of code:
if ([[[results objectAtIndex:0] name] caseInsensitiveCompare:passdTextToSearchFor] == 0)
If the array is initialized and has zero elements, you'll pass the nil check, but you'll throw an exception when trying to access any objects within the array itself.
- (NSArray *)executeFetchRequest:(NSFetchRequest *)request error:(NSError **)error can return an empty array. You should use NSArray's count method instead of checking to see if the array is nil.
I recommend you set a breakpoints on objc_exception_throw and [NSException raise] as well to aid you in debugging your applications. Then run a backtrace in gdb to see where the exception is being thrown to further diagnose the real problem.
Chris Hanson has a great writeup on how to accomplish they above located here
I am using following code for showing a next view(tableview) in Iphone app.
It sometime works and some time app crashes without any exception log
NSLog(#"Ok"); is log everytime
50% attempts application crashes
-(IBAction)statusInitiationButAction:(id)sender{
#try {
NSArray *tempArrIniId = [eventInitiationArray valueForKey:#"act_id"];
int s;
if([tempArrIniId containsObject:selectedInitiateId]){
s=[tempArrIniId indexOfObject:selectedInitiateId];
}
[tempArrIniId release];
NSString *selStatusId = [NSString stringWithString:[[eventInitiationArray objectAtIndex:s] objectForKey:#"status_id"]];
for (int i=0; i<[statusInitiationArray count]; i++) {
id statusDict = [statusInitiationArray objectAtIndex:i];
if ([selStatusId intValue] == [[statusDict objectForKey:#"id"] intValue]) {
[statusDict setValue:#"1" forKey:#"selected"];
[statusInitiationArray replaceObjectAtIndex:i withObject:statusDict];
}else {
[statusDict setValue:#"0" forKey:#"selected"];
[statusInitiationArray replaceObjectAtIndex:i withObject:statusDict];
}
}
NSLog(#"statusInitiationTable...%#",statusInitiationArray);
[statusInitiationTable.tableView reloadData];
[[self navigationController] pushViewController:statusInitiationTable animated:YES];
NSLog(#"ok");
}#catch (NSException * e) {
NSLog(#"statusInitiationButAction....%#",e);
}
}
Can anybody guide me about the problem.
Thanks
Amit Battan
You should not be doing this:
[tempArrIniId release];
Because in this line...
NSArray *tempArrIniId = [eventInitiationArray valueForKey:#"act_id"];
...you are not creating the tempArrIniId you are merely obtaining a reference to it. Therefore, you did not retain it and have no need to release it.
You are getting an intermittent crash because your over releasing the object pointed to by tempArrIniId while that object is still a member of the eventInitiationArray. When the array tries to access the object or even count itself, it crashes because there is not an object where it expects one to be. That crash can happen anywhere in the app where the 'eventInitiationArray' is used.
Overzealous releasing causes more problems than it prevents. When in doubt, don't release. It's trivial to find memory leaks with the analysis tools if you don't release something you should have and it's trivial to fix it.
It's a lot harder to track down a crash caused by over releasing an object held by other objects such as arrays because the subsequent crash can occur far from where the over release occurred.