2010-04-25 7 views
9

Estoy tratando de obtener datos de un sitio web- xml. Todo funciona bienAsync llamada Objective C iphone

Pero el UIButton permanece presionado hasta que se devuelven los datos xml y, por lo tanto, si hay un problema con el servicio de Internet, no se puede corregir y la aplicación es prácticamente inutilizable.

aquí son las llamadas:

{ 
    AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate]; 
    if(!appDelegate.XMLdataArray.count > 0){ 
     [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; 
     [appDelegate GetApps]; //function that retrieves data from Website and puts into the array - XMLdataArray. 

    } 
    XMLViewController *controller = [[XMLViewController alloc] initWithNibName:@"MedGearsApps" bundle:nil]; 
    [self.navigationController pushViewController:controller animated:YES]; 
    [controller release]; 
} 

Funciona bien, pero ¿cómo puedo hacer que los botones de vista funcional con quedarse atascado. En otras palabras, solo quiero que UIButton y otros UIButtons sean funcionales mientras la cosa funcione en segundo plano.

oí acerca performSelectorInMainThread, pero no puedo ponerlo en práctica correctamente

cualquier ayuda se agradece :)

Respuesta

36

No entiende mucho el modelo de subprocesamiento y es probable que se pegue un tiro en el pie si comienza a agregar código asíncrono sin comprender realmente lo que está sucediendo.

El código que escribió se ejecuta en el subproceso principal de la aplicación. Pero cuando lo piensas, no tienes que escribir la función main; solo implementas el delegado de la aplicación y las devoluciones de eventos (como los controladores táctiles) y de alguna manera se ejecutan automáticamente cuando llega el momento. Esto no es una magia, este es simplemente un objeto Cocoa llamado Run Loop.

Run Loop es un objeto que recibe todos los eventos, procesa los temporizadores (como en NSTimer) y ejecuta su código. Lo que significa que cuando, por ejemplo, hacer algo cuando el usuario pulsa un botón, el árbol de llamadas se ve un poco como esto:

main thread running 
    main run loop 
     // fire timers 
     // receive events — aha, here we have an event, let’s call the handler 
     view::touchesBegan… 
      // use tapped some button, let’s fire the callback 
      someButton::touchUpInside 
       yourCode 

Ahora yourCode hace lo que quiere hacer y el bucle de ejecución continúa en funcionamiento. Pero cuando el código tarda demasiado en finalizar, como en su caso, Run Loop debe esperar y, por lo tanto, los eventos no se procesarán hasta que el código finalice. Esto es lo que ves en tu aplicación.

Para resolver la situación, debe ejecutar la operación larga en otra secuencia. Esto no es muy difícil, pero tendrá que pensar en algunos problemas potenciales, sin embargo.Correr en otro hilo puede ser tan fácil como llamar performSelectorInBackground:

[appDelegate performSelectorInBackground:@selector(GetApps) withObject:nil]; 

Y ahora hay que pensar en una manera de decirle a la aplicación de los datos se ha cargado, como el uso de una notificación o llamar a un selector de la principal hilo. Por cierto: almacenar los datos en el delegado de la aplicación (o incluso usar el delegado de la aplicación para cargar los datos) no es una solución muy elegante, pero esa es otra historia.

Si elige la solución performSelectorInBackground, consulte una pregunta relacionada con memory management in secondary threads. Necesitarás tu propio grupo de autorreleases para que no se filtren objetos liberados automáticamente.


Actualización de la respuesta después de algún tiempo - hoy en día por lo general es mejor para ejecutar el código en el fondo utilizando Grand Central Dispatch:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
    // No explicit autorelease pool needed here. 
    // The code runs in background, not strangling 
    // the main run loop. 
    [self doSomeLongOperation]; 
    dispatch_sync(dispatch_get_main_queue(), ^{ 
     // This will be called on the main thread, so that 
     // you can update the UI, for example. 
     [self longOperationDone]; 
    }); 
}); 
+0

Sí, leí sobre estas cosas, todavía hay mucho más que entender, pero lo entenderé, me has puesto en la dirección correcta. Gracias. Estaba usando el componente TBXML, así que supongo que necesito escribir uno simple para entender cómo funciona. de todos modos, Otra pregunta relacionada fue, si el usuario elige cerrar la vista, ¿hay alguna forma de terminar la llamada selectorInBackground? en ViewDidUnload ..? – Sam

+1

Eso depende. En casos simples, puede ignorar el problema y codificar la parte de notificación para que no se rompa cuando la vista se cierre antes. Se puede escribir una solución más a prueba de balas usando el modo asíncrono 'NSURLConnection' o la cola de operaciones, ambos tienen un método para cancelar la operación en progreso. – zoul

+0

El último fragmento de código es increíble, es exactamente lo que estoy buscando. ¡Muchas gracias! – kisileno

0

Usted puede hacer uso de una operación en segundo plano que es empujado en la cola de la operación:

BGOperation *op = [[BGOperation alloc] init]; 
[[self operationQueue] addOperation:op]; 
[op release]; 

he creado "comandos" específicos que se ejecutan en segundo plano:

@implementation BGOperation 

# pragma mark Memory Management 

- (BGOperation *)init 
{ 
if ((self = [super init]) != nil) 
    /* nothing */; 
return self; 
} 

- (void)dealloc 
{ 
self.jobId = nil; 
[super dealloc]; 
} 

# pragma mark - 
# pragma mark Background Operation 

- (void)main 
{ 
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 
    [appDelegate GetApps]; 
[pool release]; 
return; 
} 

@end

Después de la finalización, puede ser una buena idea enviar una notificación al hilo principal porque la base de datos interna ha sido modificada.

+1

¿No es mucho más fácil de usar 'performSelectorInBackground' o asíncrona' ¿Modo NSURLConnection'? – zoul

+1

No, porque entonces debe manejar el ciclo de vida del subproceso (en el ejemplo dado, no necesitó crear su propio grupo de autorrelease). El NSUrlConnection de Asynch está bien, pero funciona en el hilo principal y puede hacer que su UI tartamudee, especialmente si tiene algunos de ellos funcionando a la vez. OperationQueues es mucho, mucho más fácil de trabajar y administrar ... –

+0

Crear y drenar un grupo de autorrelease es dos líneas de código, no me parece un gran problema. (Solo un punto de vista.) – zoul

0

Parece que podría estar utilizando NSURLConnection dentro de su método getApps. De ser así, conviértalo en una llamada asíncrona.

3

Utilice el método connectionWithRequest:delegate: de NSURLConnection. Esto hará que la solicitud especificada se envíe de forma asíncrona. El delegado debe responder a la conexión: didReceiveResponse: y se le enviará ese mensaje una vez que la respuesta se haya recibido por completo.

Cuestiones relacionadas