2009-06-03 12 views
8

Tengo un UIWebView en un controlador de vista, que tiene dos métodos como a continuación. La pregunta es si salgo (pulse atrás en la barra de navegación) este controlador antes de que termine el segundo hilo, la aplicación se bloqueará después de [super dealloc], porque "Intenté obtener el bloqueo web de un hilo que no sea el hilo principal o el hilo web. Esto puede ser el resultado de llamar a UIKit desde un hilo secundario ". Cualquier ayuda sería realmente apreciada.UIWebView en multihilo ViewController

-(void)viewDidAppear:(BOOL)animated { 
    [super viewWillAppear:animated]; 
    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(load) object:nil]; 
    [operationQueue addOperation:operation]; 
    [operation release]; 
} 

-(void)load { 
    [NSThread sleepForTimeInterval:5]; 
    [self performSelectorOnMainThread:@selector(done) withObject:nil waitUntilDone:NO]; 
} 
+0

[mi solución (utiliza un NSTimer para la última versión)] (http://stackoverflow.com/questions/6353471/block-release-deallocating-ui-objects-on-a-background -thread/6482941 # 6482941 "Mi solución") – Jeff

Respuesta

35

Tenía la misma solución en la que un hilo de fondo era la última versión, lo que causaba que se desglosara el controlador de vista en una secuencia de fondo que termina con el mismo bloqueo.

La anterior [[self retain] autorelease] aún resultaría en la liberación final del grupo de autorrelease del hilo de fondo. (A menos que haya algo especial acerca de las versiones del grupo de autorrelease, me sorprende que esto haga la diferencia).

I encontraron esta como mi solución ideal, la colocación de este código en mi vista de clase controlador:

- (oneway void)release 
{ 
    if (![NSThread isMainThread]) { 
     [self performSelectorOnMainThread:@selector(release) withObject:nil waitUntilDone:NO]; 
    } else { 
     [super release]; 
    } 
} 

Esto asegura que el método de mi clase de controlador de vista release siempre se ejecuta en el hilo principal.

Estoy un poco sorprendido de que ciertos objetos que sólo pueden ser dealloc'ed correctamente desde el hilo principal no tienen ya algo como esto incorporada. Oh, bueno ...

+0

Esto terminó un gran dolor de cabeza para mí. ¡Gracias! – spstanley

+0

Me alegro de haberlo encontrado - Abril de 2011 – GuybrushThreepwood

+0

¡¡¡Gracias muchísimo !! –

1

En general, debe cancelar las operaciones en segundo plano cuando se va la vista que las utiliza. Como en:

- (void)viewWillDisappear:(BOOL)animated { 
    [operationQueue cancelAllOperations]; 
    [super viewWillDisappear:animated; 
} 
+0

Gracias por responder. Pero he agregado eso y parece que se llama siempre a dealloc en el hilo secundario. Todavía no puedo entender la razón. – Tao

+0

La cancelación de operaciones en segundo plano es la forma "correcta" de hacerlo. La razón por la que no resuelve el problema es porque 'cancelAllOperations' no cancela automáticamente las operaciones que ya se están ejecutando. Lo que debe hacer es utilizar el método 'load', después de que el thread reposa, verifique la propiedad' isCancelled' de la operación; si es así, regrese sin llamar 'hecho'. Pero si todo lo que hace es simplemente hacer una pausa en un hilo de fondo, una forma más fácil de hacerlo es con 'performSelector: withObject: afterDelay:'. –

0

No estoy seguro exactamente lo que está pasando en base a su código, pero parece que viewDidAppear está recibiendo llamadas y la creación del segundo hilo, y luego estás navegando lejos del controlador y liberándolo , y luego el segundo subproceso finaliza y llama a performSelectorOnMainThread en el objeto liberado "self". Creo que tal vez solo necesites verificar que la liberación no haya ocurrido.

El mensaje de error que está recibiendo implica que está ejecutando algún código UIKit desde su segundo hilo. Recientemente, Apple agregó algunas comprobaciones de llamadas enrutadas a UIKit, y creo que probablemente solo necesite refactorizar su función de carga para actualizar la UI en el hilo principal, en lugar de llamar a las funciones de UIWebView desde el segundo hilo.

Espero que ayude!

1

Actualmente tengo un problema similar en mi aplicación. Un controlador de vista que muestra un UIWebView se envía a un controlador de navegación e inicia una cadena de fondo para recuperar datos. Si presiona el botón Atrás antes de que el hilo haya terminado, la aplicación se bloquea con el mismo mensaje de error.

El problema parece ser que NSThread conserva el objetivo (self) y el objeto (argumento) y lo libera después de que se haya ejecutado el método; desafortunadamente libera ambos desde el hilo. Entonces, cuando se crea el controlador, el recuento de retención es 1, cuando se inicia el subproceso, el controlador obtiene un conteo de retención de 2. Cuando apaga el controlador antes de que termine el subproceso, el controlador de navegación libera el controlador, lo que da como resultado retenga el recuento de 1. Hasta ahora, esto está bien, pero si el hilo finalmente finaliza, NSThread libera el controlador, lo que da como resultado un conteo de retención de 0 y un desglose inmediato desde el hilo. Esto hace que UIWebView (que se lanza en el método dealloc del controlador) levante esa excepción de advertencia de hilos y cuelgue.

Funcioné satisfactoriamente al utilizar [[self retain] autorelease] como la última instrucción en el hilo (justo antes de que el hilo libere su grupo). Esto garantiza que el objeto del controlador no se libere inmediatamente, sino que se marque como liberado automáticamente y desasignado más adelante en el ciclo de ejecución del subproceso principal. Sin embargo, este es un truco algo sucio y prefiero encontrar una mejor solución.

+0

Su solución de 'trabajo alrededor' funciona para mí, gracias. BTW, Tao, ¿cuál es su última decisión sobre qué método utiliza? – RoundOutTooSoon

+1

en realidad, estás perdiendo memoria ... !! – Ricibald

0

Intenté las dos soluciones publicadas anteriormente, [operationQueue cancelAllOperations] y [[self retain] autorelease]. Sin embargo, con un clic rápido, todavía hay casos en que el conteo de retención cae a 0 y la clase se desasigna en el hilo secundario. Con el fin de evitar un choque, pongo el siguiente en mi dealloc por ahora:

if ([NSThread isMainThread]) { 
     [super dealloc]; 
    } 

que es una fuga obvio, pero parece ser el menor de 2 males.

Cualquier información adicional de cualquier persona que encuentre este problema es bienvenida.

1

me trataron:

[self retain]; 
[self performSelectorOnMainThread:@selector(release) withObject:nil waitUntilDone:NO]; 

que parece funcionar aún mejor.

12

Aquí hay un código de ejecuta elementos UIKit en el hilo principal. Si está trabajando en un hilo diferente y necesita ejecutar una parte del código de UIKit, simplemente colóquelo entre los corchetes de este fragmento de Grand Central Dispatch.

dispatch_async(dispatch_get_main_queue(), ^{ 

    // do work here 

}); 
0
- (void)dealloc 
{ 
    if(![NSThread isMainThread]) { 
     [self performSelectorOnMainThread:@selector(dealloc) 
           withObject:nil 
          waitUntilDone:[NSThread isMainThread]]; 
     return; 
    } 
    [super dealloc]; 
} 
+1

cuando publica código, es preferible agregar una breve explicación – ronalchn