Bucle y conexiones asincrónicas en el objective c

Tengo un set de nombres de tablas y deseo recorrerlos y tomar su nombre y agregar una URL para crear una connection a un service web para download datos JSON. El ciclo parece funcionar al principio, cada uno de los tres nombres de tabla en el set se toma y se usa para crear una connection a un service web y los datos se descargan, pero cuando el ciclo ha finalizado (pasado de 3 a 0) aparece el bucle para comenzar de nuevo y hacer un bucle infinito para las dos últimas tablas de la matriz.

Salida de logging (observe que el altavoz y el expositor se repiten una y otra vez):

2013-12-16 10:38:08.755 WebServiceTest[501:60b] loopCount = 3 2013-12-16 10:38:08.758 WebServiceTest[501:60b] table name = workshop 2013-12-16 10:38:08.817 WebServiceTest[501:60b] LoopCount is: 2 2013-12-16 10:38:08.821 WebServiceTest[501:60b] loopCount = 2 2013-12-16 10:38:08.822 WebServiceTest[501:60b] table name = exhibitor 2013-12-16 10:38:08.827 WebServiceTest[501:60b] LoopCount is: 1 2013-12-16 10:38:08.830 WebServiceTest[501:60b] loopCount = 1 2013-12-16 10:38:08.831 WebServiceTest[501:60b] table name = speaker 2013-12-16 10:38:08.835 WebServiceTest[501:60b] LoopCount is: 0 2013-12-16 10:38:09.199 WebServiceTest[501:3707] Status code = 200 2013-12-16 10:38:09.204 WebServiceTest[501:3707] Objects in current table - speaker = 1 2013-12-16 10:38:09.207 WebServiceTest[501:3707] Status code = 200 2013-12-16 10:38:09.210 WebServiceTest[501:3707] Objects in current table - exhibitor = 2 2013-12-16 10:38:09.229 WebServiceTest[501:450b] Status code = 200 2013-12-16 10:38:09.234 WebServiceTest[501:450b] Objects in current table - speaker = 1 2013-12-16 10:38:09.240 WebServiceTest[501:3707] Status code = 200 2013-12-16 10:38:09.244 WebServiceTest[501:3707] Objects in current table - exhibitor = 2 2013-12-16 10:38:09.271 WebServiceTest[501:450b] Status code = 200 2013-12-16 10:38:09.274 WebServiceTest[501:450b] Objects in current table - speaker = 1 2013-12-16 10:38:09.294 WebServiceTest[501:450b] Status code = 200 2013-12-16 10:38:09.298 WebServiceTest[501:4803] Status code = 200 2013-12-16 10:38:09.302 WebServiceTest[501:4803] Objects in current table - exhibitor = 2 2013-12-16 10:38:09.309 WebServiceTest[501:4803] Status code = 200 2013-12-16 10:38:09.337 WebServiceTest[501:4803] Objects in current table - speaker = 1 2013-12-16 10:38:09.338 WebServiceTest[501:4803] Status code = 200 2013-12-16 10:38:09.341 WebServiceTest[501:4803] Objects in current table - exhibitor = 2 2013-12-16 10:38:09.347 WebServiceTest[501:4803] Status code = 200 2013-12-16 10:38:09.352 WebServiceTest[501:4803] Objects in current table - speaker = 1 2013-12-16 10:38:09.311 WebServiceTest[501:450b] Objects in current table - workshop = 143 

viewController.h:

 #import <UIKit/UIKit.h> @interface ViewController : UIViewController<UITableViewDataSource, UITableViewDelegate, NSURLConnectionDataDelegate>{ NSMutableArray *arrayTable; } @property (weak, nonatomic) IBOutlet UITableView *myTableView; @property NSMutableArray *tables; @property NSMutableArray *tableNames; @property NSMutableURLRequest *request; @property NSString *tableName; -(void) startUpdate; typedef void(^completion_t)(NSArray* objects, NSError*error); -(void)fetchData:(NSString *)tableName withCompletion:(completion_t)completionHandler; -(void)fetchObjectsWithTableName:(NSString*)tableName completion:(completion_t)completionHandler; 

viewController.m:

 #import "ViewController.h" @implementation ViewController - (void)viewDidLoad { [self startUpdate]; [super viewDidLoad]; [[self myTableView]setDelegate:self]; [[self myTableView]setDataSource:self]; arrayTable =[[NSMutableArray alloc]init]; } -(void)startUpdate { NSArray* tableNames = @[@"speaker", @"exhibitor", @"workshop"]; NSUInteger loopCount = tableNames.count; while (loopCount > 0){ NSString *tableName = [tableNames objectAtIndex:loopCount -1]; NSLog(@"loopCount = %lu", (unsigned long)loopCount); NSLog(@"table name = %@", tableName); [self fetchObjectsWithTableName:[tableName mutableCopy] completion:^(NSArray* objects, NSError*error){ if (error) { NSLog(@"Error: %@", error); } else { NSLog(@"Result: %@", objects); } }]; loopCount --; NSLog(@"LoopCount is: %i", loopCount); } } -(void)fetchObjectsWithTableName:(NSString*)tableName completion:(completion_t)completionHandler; { [self fetchData:tableName withCompletion:^(NSArray* objects, NSError*error){ if (objects) { [self.tables addObject:objects]; [self fetchObjectsWithTableName:tableName completion:completionHandler]; } else if (objects == nil){ NSLog(@"objects = %@", objects); NSLog(@"break out of FOWTN"); } else { NSLog(@"Objects is something else..."); } }]; if (completionHandler) { completionHandler(self.tables, nil); } } -(void)fetchData:(NSString *)tableName withCompletion:(completion_t)completionHandler { NSString *currentURL = [NSString stringWithFormat:@"https://testapi.someURL.com/api/congress/%@", tableName]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:currentURL]]; [request addValue:@"application/json" forHTTPHeaderField:(@"Accept")]; [NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { NSError* err = error; NSArray* objects; // final result array as a representation of JSON Array if (response) { NSHTTPURLResponse *newResp = (NSHTTPURLResponse*)response; if (newResp.statusCode == 200) { NSLog(@"Status code = %li", (long)newResp.statusCode); if ([data length] >0 && error == nil) { NSError* localError; objects = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error]; if (objects) { if (completionHandler) { completionHandler(objects, nil); } //NSLog(@"Objects in current table - %@ = %@", tableName, objects); NSLog(@"Objects in current table - %@ = %lu", tableName, (unsigned long)objects.count); return; } else { err = localError; } } else { // err = ... } } } if (objects == nil) { NSLog(@"Nothing found in table: %@", tableName); //assert(err); if (completionHandler) { completionHandler(nil, err); } } }]; } 

(Editar: eliminado)

En mi humilde opinión, el bucle se ve mejor con un bucle for, que itera en la dirección "correcta":

 -(void)startUpdate { NSUInteger count = tableNames.count; for (int i = 0; i < count; ++i){ NSString *tableName = [tableNames objectAtIndex:i]; ... } } 

Algunas sugerencias adicionales:

Habiendo dicho esto y arreglado este problema, ahora, debe darse count de que está invocando methods asynchronouss dentro del bloque del bucle. Por lo tanto, su método startUpdate se vuelve asíncrono.

Si desea que el sitio de llamadas reciba una notificación cuando se hayan terminado todos los methods asíncronos, su startUpdate puede usar un controller de finalización:

 - (void) startUpdateWithCompletion:(completion_t)completionHandler; 

Puede invocar methods asynchronouss en un for loop . Pero efectivamente, esto procesará todas las tareas asíncronas en paralelo , a less que las tareas asíncronas subyacentes se encarguen de ejecutarlas en una queue compartida privada cuyo "tamaño" (número de operaciones simultáneas) es configurable.

Una implementación concreta adecuada ahora depende de sus requisitos, es decir, si necesita controlar el número de tareas simultáneas en ejecución y dónde desea lograr esto. También depende de si desea poder cancelar el ciclo desde cualquier otro hilo en cualquier momento, si fuera necesario.

Por ejemplo:

Supongamos que no hacemos suposiciones acerca de las tareas asíncronas subyacentes, lo que significa que no utilizarán una queue privada compartida para limitar el número de tareas simultáneas en ejecución por sí mismo. Además, desea asegurarse de que todas las tareas se ejecuten una tras otra , o en serie .

Un enfoque (fuera de severals) es usar un NSOperationQueue , establecer su número máximo de operaciones en uno, y agregar sus tareas asíncronas que necesitan ser envueltas en una NSOperation "concurrente".

Un NSOperation "concurrente" es una operación que debe iniciarse con el método start , que es asíncrono. Básicamente, la operación finalizará cuando se complete su tarea asíncrona. Cuando se agrega a un NSOperationQueue , la queue se encarga de cuándo iniciar la operación.

Desafortunadamente, subclasificar una NSOperation como una operación simultánea requiere cuidar algunas sutilezas. La documentation oficial Subclassing Notes no los describe con gran detalle, por lo que puede echar un vistazo aquí a este fragment de código: Implantación canónica de una subclass de NSOperation .

Ahora, supongamos que tiene una subclass correctamente de un NSOperation , NSOperation FetchTableOperation :

 completion_t completionHandler = ^(..) {..}; NSOperationQueue* queue = [[NSOperationQueue alloc] init]; queue.maxConcurrentOperations = 1; for (NSString tableName in self.tableNames) { FetchTableOperation* op = [[FetchTableOperation alloc] initWithName:tableName completion: ^{...}]; [queue addOperation:op]; } 

Para recibir una notificación cuando las operaciones estén terminadas agregue un bloque "centinela":

 [queue addOperationWithBlock:^{ // finished }]; 

Advertencia:

  • Necesita crear una subclass adecuada de NSOperation concurrente para ajustar su método asynchronous.

  • Recibe una notificación cuando finalizó la última operación y NO cuando finalizó el bloque de finalización de la última operación.

  • ¡Los manejadores de finalización aún pueden ejecutarse en paralelo! (a less que se ejecuten en el hilo principal o en una queue privada cuyo tamaño sea igual a uno)

  • Todas las tareas serán enqueuedas , lo cual no es un problema, a less que el número de tareas sea realmente grande. Cada tarea enqueueda simplemente consumirá un poco de RAM del sistema.

La ventaja de utilizar un NSOperationQueue es que puede cancelar las operaciones pendientes en cualquier momento. Además, puede ajustar el tamaño de la queue, es decir, el número de operaciones simultáneas máximas con bastante facilidad con la propiedad maxConcurrentOperations .

Otros enfoques:

Uso de un grupo de dispatch Ejecución de todas las tareas simultáneamente

Por el contrario, esta es una solución rápida y fácil. Sin embargo, sus tareas se iniciarán en paralelo:

 dispatch_group group = dispatch_group_create(); for (NSString* tableName in self.tableNames) { dispatch_group_enter(group); [self fetchObjectsWithTableName:tableName completion:^{ ... dispatch_group_leave(group); }]; } dispatch_group_notify(group, dispatch_get_main_queue(), ^{ ... // all tasks *and* all completion handler finished }); 

"Asynchronous Loop" que ejecuta secuencialmente las tareas asíncronas:

Esta es también una solución bastante simple, una vez que entiendas el patrón.

¿Cómo download varias imágenes de forma asíncrona en iOS sin afectar a la interfaz de usuario?

Utilizando una categoría NSArray que invoca secuencialmente tareas asíncronas:

Este es un componente "reutilizable", que se puede utilizar fácilmente, una vez que lo haya implementado. Puede usarlo de la siguiente manera:

 typedef void (^unary_async_t)(id input, completion_t completion); typedef void (^completion_t)(id result); unary_async_t task = ^(id input, completion_t completionHandler) { [self fetchObjectsWithTableName:input completion:^(NSData* result, NSError*error){ if (error == nil) { ... ; } completionHandler(error ? error : @"OK"); }]; }; NSArray* tableNames = @[@"speaker", @"exhibitor", @"workshop"]; [tableNames forEachApplyTask:task completion:^(id result){ // result is an array containing the result of each operation in the same order ... }]; 

https://gist.github.com/couchdeveloper/6155227

Estás llamando a la operación de bloque dentro while loop. La operación no se pudo completar (y probablemente no esté completa. Debe usar repetición aquí. Lo escribo en pesudocode:

  1. Tienes una matriz con algún elemento para la connection.
  2. Usted dispara la operación de locking en un método separado llamado fetchWith:
  3. En complete después del locking, disparas el mismo método si el índice + 1 aún aparece en la matriz.
  4. Si disparas una connection, deja el método uno por uno.

Con este enfoque, usted dispara el método solo una vez y la repetición se ocupa del rest.

Si desea disparar varias conexiones de la misma hora, probablemente debería considerar delegates.

EDIT: Ok, así es como puedes hacer esto:

 -(void)fetchNowWithIndex:(NSInteger)index { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSData *downloadData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[@"yourURL"]]; dispatch_async(dispatch_get_main_queue(), ^{ //refresh label here [self.arrayWithLabelsToChange replaceObjectAtIndex:index]; [self fetchNowWithIndex:index+1] }); }); }