¿Acelera la búsqueda usando dispatch_async?

Estoy tratando de acelerar la búsqueda de aplicaciones, se retrasa cuando hay muchos datos.

así que estoy tratando de dividir el Pnetworkingicado de búsqueda en la interfaz de usuario mediante el uso de dispatch_async no dispatch_sync porque no es diferente si lo uso.

El problema es que cuando uso dispatch_async , la aplicación falla a veces porque [__NSArrayI objectAtIndex:]: index "17" beyond bounds .

Ahora esto sucedió porque vamos a decir que el primero sigue funcionando y volver a cargar la tablaView y continuar la búsqueda cambiará el tamaño de la matriz depende del resultado, entonces en este caso "CRASH" 🙁

este es mi código:

  dispatch_async(myQueue, ^{ searchArray = [PublicMeathods searchInArray:searchText array:allData]; } ); if(currentViewStyle==listViewStyle){ [mytable reloadData]; } 

y he intentado esto:

  dispatch_async(myQueue, ^{ NSArray *tmpArray = [PublicMeathods searchInArray:searchText array:allData]; dispatch_sync(dispatch_get_main_queue(), ^{ searchArray = tmpArray; [mytable reloadData]; }); }); 

pero en este caso los rezagos todavía están allí.

Actualización -1-:

El Pnetworkingicate de búsqueda tarda solo 2 ms 🙂 después del trabajo duro 🙂 pero el keyboard todavía se retrasa cuando el usuario busca, así que lo único que hago después de get el resultado es volver a cargar la tabla "cambiar en la interfaz de usuario", esto creo que lo hace retrasado,

Entonces, lo que busco para dividir esta operación dos "escribir en el keyboard y actualizar la interfaz de usuario".

Actualización -2-:

@matehat https://stackoverflow.com/a/16879900/1658442

y

@ TomSwift https://stackoverflow.com/a/16866049/1658442

las respuestas funcionan como un encanto 🙂

Una solución podría consistir en inducir voluntariamente un retraso entre las búsquedas para permitir que el usuario escriba y permita que la búsqueda se realice de forma asíncrona. Así es cómo:

Primero asegúrate de que tu queue esté creada de esta manera:

 dispatch_queue_t myQueue = dispatch_queue_create("com.queue.my", DISPATCH_QUEUE_CONCURRENT); 

Haga que este ivar se defina en su class (y configúrelo en FALSE al inicializarse):

 BOOL _scheduledSearch; 

Anote esta macro en la parte superior de su file (o en cualquier lugar realmente, solo asegúrese de que esté visible)

 #define SEARCH_DELAY_IN_MS 100 

Y en lugar de su segundo fragment, llame a este método:

 [self scheduleSearch]; 

Cuya implementación es:

 - (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]; }); } 

[self textToSearchFor] es de donde debe get el text de búsqueda real.

Esto es lo que hace:

  • La primera vez que ingresa una request, establece el _scheduledSearch iv_scheduledSearch en TRUE y le dice a GCD que programe una búsqueda en 100 ms
  • Mientras tanto, no se atiende ninguna request de búsqueda nueva, ya que de todos modos una búsqueda se realizará en unos pocos ms.
  • Cuando ocurre la búsqueda progtwigda, el _scheduledSearch ivar se restablece a FALSE , por lo que se maneja la siguiente request.

Puede jugar con diferentes valores para SEARCH_DELAY_IN_MS para que se adapte a sus necesidades. Esta solución debe desacoplar por completo los events del keyboard con la carga de trabajo generada por la búsqueda.

Si searchArray es la matriz que se utiliza como origen de datos de vista de tabla, solo se debe acceder y modificar esta matriz en el hilo principal.

Por lo tanto, en el subprocess de background, primero debe filtrarse en un set temporal diferente. A continuación, asigna la matriz temporal a searchArray en el hilo principal:

 dispatch_async(myQueue, ^{ NSArray *tmpArray = [PublicMeathods searchInArray:searchText array:allData]; dispatch_sync(dispatch_get_main_queue(), ^{ searchArray = tmpArray; [mytable reloadData]; }); }); 

Actualización: el uso de una matriz temporal debe resolver el problema del locking, y el uso de un hilo de background ayuda a mantener la interfaz de usuario sensible durante la búsqueda. Pero como resultó en la discusión, una razón importante para la búsqueda lenta podría ser la complicada lógica de búsqueda.

Puede ayudar a almacenar datos "normalizados" adicionales (por ejemplo, todos convertidos a minúsculas, numbers de teléfono convertidos a un formulario estándar, etc.) para que la búsqueda real se pueda hacer con comparaciones más rápidas y sin distinción entre mayúsculas y minúsculas.

Primero, un par de notas sobre el código que presentaste:

1) Parece que es probable que haga varias búsquedas en queue cuando el usuario escribe, y todas deben ejecutarse hasta completarse antes de que la relevante (la más reciente) actualice la pantalla con el set de resultados deseado.

2) El segundo fragment que muestra es el patrón correcto en términos de security de hilo. El primer fragment actualiza la interfaz de usuario antes de que se complete la búsqueda. Probablemente su falla ocurra con el primer fragment porque el hilo de background actualiza el array de búsqueda cuando el hilo principal está leyendo de él, lo que significa que su fuente de datos (respaldada por searchArray) está en un estado inconsistente.

No dice si está utilizando un UISearchDisplayController o no, y realmente no importa. Pero si lo está, un problema común no está implementando - (BOOL) searchDisplayController: (UISearchDisplayController *) controller shouldReloadTableForSearchString: (NSString *) filter y returning NO. Al implementar este método y devolver NO, está desactivando el comportamiento pnetworkingeterminado de volver a cargar el tableView con cada cambio en el término de búsqueda. En cambio, tiene la oportunidad de iniciar su búsqueda asincrónica del nuevo término y actualizar la UI ( [tableview reloadData] ) solo una vez que tenga nuevos resultados.

Independientemente de si está utilizando UISearchDisplayController , debe tener en count algunas cosas al implementar su búsqueda asíncrona:

1) Idealmente, puede interrumpir una búsqueda en curso y cancelarla si la búsqueda ya no es útil (por ejemplo, el término de búsqueda cambiado). Su método 'searchInArray' no parece admitir esto. Pero es fácil de hacer si simplemente escanea una matriz.

1a) Si no puede cancelar su búsqueda, todavía necesita una forma al final de la búsqueda para ver si sus resultados son relevantes o no. Si no es así, entonces no actualice la interfaz de usuario.

2) La búsqueda debe ejecutarse en un subprocess de background para no empantanar el subprocess principal y la interfaz de usuario.

3) Una vez que la búsqueda finaliza, debe actualizar la interfaz de usuario (y la fuente de datos de la interfaz de usuario) en el hilo principal.

Armaré un proyecto de ejemplo ( aquí, en Github ) que realiza una búsqueda bastante ineficiente contra una gran list de palabras. La interfaz de usuario sigue respondiendo a medida que el usuario escribe su término y las búsquedas generadas se cancelan a medida que se vuelven irrelevantes. La carne de la muestra es este código:

 - (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* filtenetworkingWords = [NSMutableArray arrayWithCapacity: _allWords.count]; // only using a NSPnetworkingicate here because of the SO question... NSPnetworkingicate* p = [NSPnetworkingicate pnetworkingicateWithFormat: @"SELF CONTAINS[cd] %@", filter]; // this is a slow search... scan every word using the pnetworkingicate! [_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] ) { [filtenetworkingWords 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(), ^{ _filtenetworkingWords = filtenetworkingWords; [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; } } 

Creo que su falla se resuelve mediante la inclusión de la visualización del elemento UI para el cual searchArray es el elemento de respaldo en una llamada a GrandCentralDispatch dentro de la otra llamada (como se muestra en su publicación original actualizada). esa es la única forma de asegurarse de que no está causando que los elementos de la matriz cambien entre bastidores mientras se produce la visualización de los elementos asociados con ella.

sin embargo, creo que si está viendo retraso, no está causado tanto por el procesamiento de la matriz en 2 ms ni por la recarga que tarda 30 ms, sino por el time que tarda GCD en llegar a la llamada interna dispatch_sync en la queue principal .

si, en este punto, ha logrado networkingucir el procesamiento de su matriz a solo 2 ms en el peor de los casos (o incluso si ha logrado networkingucirlo a less de 30 ms, lo cual es aproximadamente el time que lleva procesar un fotogtwig en el bucle principal de ejecución a 30 fps), entonces debería considerar abandonar completamente el GCD en su esfuerzo por procesar este set. tomar 2 ms en la queue principal para procesar su matriz no causará ningún comportamiento defectuoso.

es posible que tenga un rezago en otro lugar (es decir, si está incrementando los resultados de búsqueda tratando de salir a la networking para get los resultados, es posible que desee hacer la llamada y luego procesar la respuesta en su queue de envío por separado), pero para las veces que estamos hablando, este bit de procesamiento no necesita dividirse en queues separadas. para cualquier procesamiento de núcleo duro que demore más de 30 ms, debe considerar GCD.

Sospecho que su problema es que allData se comparte entre la queue principal y la queue de background. Si realiza un cambio en allData en la queue principal, eso puede acortar allData en la queue de background, provocando que un índice que solía ser válido sea inválido.

También es posible que el problema no sea allData , sino algún array dentro de los objects en allData . Intente establecer un punto de interrupción en las excepciones (en Xcode, abra la list de fonts de puntos de interrupción, click el button más en la parte inferior y select "Agregar punto de exception …") para que pueda ver exactamente dónde ocurre el error.

En cualquier caso, tiene dos soluciones posibles:

  1. Copie el object ofensivo antes de utilizarlo en la búsqueda. Esto protege la queue de background de los cambios en la queue principal, pero dependiendo de lo que necesite copyr, puede ser difícil volver a introducir los cambios en la interfaz de usuario; es posible que deba hacer coincidir las copys con sus originales.

  2. Utilice un locking (como @synchronized ) o una queue por object para garantizar que solo una queue esté usando el object a la vez. NSManagedObjectContext utiliza el último enfoque para sus -performBlock: y -performBlockAndWait: Puede ser un poco difícil hacer esto sin bloquear la queue principal, sin embargo.

Intenta modificar tus funciones de la siguiente manera:

prototipo de function;

 - (void)searchInArray:searchText array:allData complete: (void (^)(NSArray *arr)) complete; 

function propia

 - (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 } 

y cuando llamas a esta function

 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]; }); }]; }); 

Espero que te ayude)

Encontré una solución simple con el mismo espíritu de la solución presentada por Matehad (espere un momento y realice una búsqueda solo si el usuario no ingresa nada más). Aquí está:

Declare 2 contadores globales y una cadena global:

int keyboardInterruptionCounter1 = 0, int keyboardInterruptionCounter2 = 0 y NSString * searchTextGlobal

En la function searchBar, haga esto:

 -(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 }); }); } } 

Explicación: La búsqueda se realiza solo si ambos contadores son iguales, esto solo sucede si el usuario ha escrito y esperado .52 seg sin escribir nada más. En cambio, si los usuarios escriben lo suficientemente rápido, entonces no se realiza ninguna consulta. La solución se puede hacer con o sin rosca.

Martin R ha publicado una respuesta correcta. Lo único que señala que en lugar de

 dispatch_sync(dispatch_get_main_queue() 

debería ser

 dispatch_async(dispatch_get_main_queue() 

El código completo en Swift sería:

  let remindersFetcherQueue = dispatch_queue_create("com.gmail.hillprincesoftware.remindersplus", DISPATCH_QUEUE_CONCURRENT) dispatch_sync(remindersFetcherQueue) { println("Start background queue") estore.fetchRemindersMatchingPnetworkingicate(remindersPnetworkingicate) { 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(). } } }