Iphone 使用dispatch\u async加快搜索速度?
我正在努力加快我的应用程序搜索,当有大量数据时,它会变得滞后 因此,我试图使用Iphone 使用dispatch\u async加快搜索速度?,iphone,ios,objective-c,ios6,grand-central-dispatch,Iphone,Ios,Objective C,Ios6,Grand Central Dispatch,我正在努力加快我的应用程序搜索,当有大量数据时,它会变得滞后 因此,我试图使用dispatch\u asyncnotdispatch\u sync在UI上拆分搜索谓词 问题是,当我使用dispatch\u async时,应用程序有时会崩溃,因为[\uu NSArrayI objectAtIndex:]:索引“17”超出了界限 我现在这样做是因为假设第一个仍然有效,重新加载tableView并继续搜索将根据结果更改数组大小,因此在本例中为“崩溃”:( 这是我的代码: dispatch_as
dispatch\u async
notdispatch\u sync
在UI上拆分搜索谓词
问题是,当我使用dispatch\u async
时,应用程序有时会崩溃,因为[\uu NSArrayI objectAtIndex:]:索引“17”超出了界限
我现在这样做是因为假设第一个仍然有效,重新加载tableView并继续搜索将根据结果更改数组大小,因此在本例中为“崩溃”:(
这是我的代码:
dispatch_async(myQueue, ^{
searchArray = [PublicMeathods searchInArray:searchText array:allData];
} );
if(currentViewStyle==listViewStyle){
[mytable reloadData];
}
我试过这个:
dispatch_async(myQueue, ^{
NSArray *tmpArray = [PublicMeathods searchInArray:searchText array:allData];
dispatch_sync(dispatch_get_main_queue(), ^{
searchArray = tmpArray;
[mytable reloadData];
});
});
但在这种情况下,差距仍然存在
更新-1-:
努力工作后,搜索谓词只需2ms:)
但是当用户搜索时,键盘仍然滞后,所以我在得到结果后唯一要做的就是重新加载表“UI中的更改”,我认为这会使它滞后
所以我搜索的是拆分这两个操作“在键盘上键入并刷新UI”
更新-2-:
@马特哈特
及
@汤姆斯威夫特
答案工作起来很有魅力:)如果searchArray
是用作表视图数据源的数组,则此数组必须
只能在主线程上访问和修改
因此,在后台线程上,应该首先过滤到一个单独的临时数组中。然后将临时数组分配给主线程上的searchArray
:
dispatch_async(myQueue, ^{
NSArray *tmpArray = [PublicMeathods searchInArray:searchText array:allData];
dispatch_sync(dispatch_get_main_queue(), ^{
searchArray = tmpArray;
[mytable reloadData];
});
});
更新:使用临时数组可以解决崩溃问题,使用后台线程有助于在搜索过程中保持UI的响应性。但在讨论中发现,搜索速度缓慢的一个主要原因可能是复杂的搜索逻辑
它可能有助于存储额外的“标准化”数据(例如,所有数据都转换为小写,电话号码转换为标准格式等),以便使用
更快的不区分大小写的比较。我相信,通过在另一个调用中嵌入对GrandCentralDispatch的调用中的UI元素的显示(如您在更新的原始帖子中所示),您的崩溃确实得到了解决。这是确保在显示与阵列关联的项目时不会导致阵列元素在幕后更改的唯一方法
但是,我相信,如果您看到延迟,它不是由2ms处理阵列或30ms重新加载引起的,而是由GCD到达主队列上的内部调度同步调用所需的时间引起的
如果在这一点上,您已经成功地将数组的处理降至最坏情况下的2MS(或者即使您已经成功地将其降到小于30MS,这大约是在30 FPS中处理主运行循环中的帧所需的时间),那么您应该考虑在处理该数组时完全放弃GCD。在主队列上花费2ms来处理阵列不会导致任何错误行为
您可能会在其他地方出现延迟(即,如果您通过尝试上网获取结果来增加搜索结果,您可能希望进行呼叫,然后在单独的调度队列中处理响应),但对于您所说的时间,这一点的处理不需要分到单独的队列中。对于任何超过30MS的硬处理,你应该考虑GCD。 < P>试着修改你的函数:下一个方法:< /P>
功能原型
- (void)searchInArray:searchText array:allData complete: (void (^)(NSArray *arr)) complete;
功能本身
- (void)searchInArray:searchText array:allData complete: (void (^)(NSArray *arr)) complete {
NSArray * array = [NSArray new];
// function code
complete(array)//alarming that we have done our stuff
}
当你调用这个函数时
dispatch_queue_t searchQueue = dispatch_queue_create("com.search",NULL);
dispatch_async(searchQueue,^{
[PublicMeathods searchInArray:searchText array:allData complete:^(NSArray *arr) {
searchArray = arr;
dispatch_async(dispatch_get_main_queue(), ^{
[myTable reloadData];
});
}];
});
(希望对您有所帮助)首先,请注意您介绍的代码:
1) 看起来您可能正在排队等待用户输入的多个搜索,在相关搜索(最近的搜索)使用所需结果集更新显示之前,这些搜索都必须运行到完成
2) 您展示的第二个代码片段是线程安全方面的正确模式。第一个代码段在搜索完成之前更新UI。很可能在第一个代码段中发生崩溃,因为在主线程读取searchArray时,后台线程正在更新searchArray,这意味着您的数据源(由searchArray支持)处于不一致的状态
你不会说你是否在使用UISearchDisplayController
,这真的不重要。但如果是,一个常见的问题是没有实现-(BOOL)searchDisplayController:(UISearchDisplayController*)控制器应该重新加载TableForSearchString:(NSString*)筛选
并返回NO。通过实现此方法并返回NO,您将关闭默认行为,即每次更改搜索词时都会重新加载tableView。相反,您有机会启动对新术语的异步搜索,并仅在获得新结果后更新UI([tableview reloadData]
)
无论您是否使用UISearchDisplayController
,在实现异步搜索时都需要考虑以下几点:
1) 理想情况下,您可以中断正在进行的搜索,并在搜索不再有用时取消搜索(例如,搜索词已更改)。您的“searchInArray”方法似乎不支持此操作。但如果你只扫描一个数组,这很容易做到
1a)如果您无法取消搜索,您仍然需要在搜索结束时找到一种方法来查看您的结果是否相关。如果没有,则不更新UI
2) 搜索应该在后台线程上运行,以避免主线程和UI陷入困境
3) 搜索完成后,需要在主线程上更新UI(以及UI的数据源)
我将示例项目()放在一起,该项目对大量的w
- (BOOL) searchDisplayController: (UISearchDisplayController *) controller
shouldReloadTableForSearchString: (NSString *) filter
{
// we'll key off the _currentFilter to know if the search should proceed
@synchronized (self)
{
_currentFilter = [filter copy];
}
dispatch_async( _workQueue, ^{
NSDate* start = [NSDate date];
// quit before we even begin?
if ( ![self isCurrentFilter: filter] )
return;
// we're going to search, so show the indicator (may already be showing)
[_activityIndicatorView performSelectorOnMainThread: @selector( startAnimating )
withObject: nil
waitUntilDone: NO];
NSMutableArray* filteredWords = [NSMutableArray arrayWithCapacity: _allWords.count];
// only using a NSPredicate here because of the SO question...
NSPredicate* p = [NSPredicate predicateWithFormat: @"SELF CONTAINS[cd] %@", filter];
// this is a slow search... scan every word using the predicate!
[_allWords enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) {
// check if we need to bail every so often:
if ( idx % 100 == 0 )
{
*stop = ![self isCurrentFilter: filter];
if (*stop)
{
NSTimeInterval ti = [start timeIntervalSinceNow];
NSLog( @"interrupted search after %.4lf seconds", -ti);
return;
}
}
// check for a match
if ( [p evaluateWithObject: obj] )
{
[filteredWords addObject: obj];
}
}];
// all done - if we're still current then update the UI
if ( [self isCurrentFilter: filter] )
{
NSTimeInterval ti = [start timeIntervalSinceNow];
NSLog( @"completed search in %.4lf seconds.", -ti);
dispatch_sync( dispatch_get_main_queue(), ^{
_filteredWords = filteredWords;
[controller.searchResultsTableView reloadData];
[_activityIndicatorView stopAnimating];
});
}
});
return FALSE;
}
- (BOOL) isCurrentFilter: (NSString*) filter
{
@synchronized (self)
{
// are we current at this point?
BOOL current = [_currentFilter isEqualToString: filter];
return current;
}
}
dispatch_queue_t myQueue = dispatch_queue_create("com.queue.my", DISPATCH_QUEUE_CONCURRENT);
BOOL _scheduledSearch;
#define SEARCH_DELAY_IN_MS 100
[self scheduleSearch];
- (void) scheduleSearch {
if (_scheduledSearch) return;
_scheduledSearch = YES;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)((double)SEARCH_DELAY_IN_MS * NSEC_PER_MSEC));
dispatch_after(popTime, myQueue, ^(void){
_scheduledSearch = NO;
NSString *searchText = [self textToSearchFor];
NSArray *tmpArray = [PublicMeathods searchInArray:searchText array:allData];
dispatch_async(dispatch_get_main_queue(), ^{
searchArray = tmpArray;
[mytable reloadData];
});
if (![[self textToSearchFor] isEqualToString:searchText])
[self scheduleSearch];
});
}
-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText{
keyboardInterruptionCounter1++;
searchTextGlobal = searchText;//from local variable to global variable
NSTimeInterval waitingTimeInSec = 1;//waiting time according to typing speed.
//waits for the waiting time
[NSTimer scheduledTimerWithTimeInterval:waitingTimeInSec target:self selector:@selector(timerSearchBar:) userInfo:nil repeats:NO];
}
-(void)timerSearchBar:(NSTimer *)timer{
keyboardInterruptionCounter2++;
// enters only if nothing else has been typed.
if (keyboardInterruptionCounter2 == keyboardInterruptionCounter1) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
(unsigned long)NULL), ^(void) {
//do the search with searchTextGlobal string
dispatch_async(dispatch_get_main_queue(), ^{
//update UI
});
});
}
}
dispatch_sync(dispatch_get_main_queue()
dispatch_async(dispatch_get_main_queue()
let remindersFetcherQueue = dispatch_queue_create("com.gmail.hillprincesoftware.remindersplus", DISPATCH_QUEUE_CONCURRENT)
dispatch_sync(remindersFetcherQueue) {
println("Start background queue")
estore.fetchRemindersMatchingPredicate(remindersPredicate) {
reminders in
// var list = ... Do something here with the fetched reminders.
dispatch_async(dispatch_get_main_queue()) {
self.list = list // Assign to a class property
self.sendChangedNotification() // This should send a notification which calls a function to ultimately call setupUI() in your view controller to do all the UI displaying and tableView.reloadData().
}
}
}