2009-06-03 12 views
8

Ho un UIWebView in un viewcontroller, che ha due metodi come di seguito. La domanda è: se faccio uscire (tocca indietro sulla barra di navigazione) questo controller prima che il secondo thread sia terminato, l'app si bloccherà dopo [super dealloc], perché "Ho cercato di ottenere il blocco web da un thread diverso dal thread principale o il thread Web. Questo potrebbe essere il risultato di una chiamata a UIKit da un thread secondario. ". Qualsiasi aiuto sarebbe molto apprezzato.UIWebView in multithread 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

[La mia soluzione (utilizza un NSTimer per l'ultima release)] (http://stackoverflow.com/questions/6353471/block-release-deallocating-ui-objects-on-a-background -thread/6482941 # 6482941 "La mia soluzione") – Jeff

risposta

35

Avevo la stessa soluzione in cui un thread in background era l'ultima versione, causando dealloc del controller di visualizzazione in un thread in background che terminava con lo stesso crash.

Il precedente numero [[self retain] autorelease] comporterebbe comunque il rilascio finale del pool di autorelease del thread in background. (A meno che non ci sia qualcosa di speciale sulle versioni del pool di autorelease, sono sorpreso che questo farebbe la differenza).

ho trovato questo come mia soluzione ideale, ponendo questo codice nella mia vista classe controllore:

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

Questo assicura che il metodo della mia classe vista controllore release viene sempre eseguito sul thread principale.

Sono un po 'sorpreso che alcuni oggetti che possono essere correttamente dealloc'ed solo dal thread principale non hanno già qualcosa di simile costruito in. Vabbè ...

+0

Questo mi ha causato un forte mal di testa. Grazie! – spstanley

+0

Contento di averlo trovato - Aprile 2011 – GuybrushThreepwood

+0

Grazie mille !! –

1

In generale, si dovrebbe annullare qualsiasi operazioni in background quando la vista che li utilizza sta andando via. Come in:

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

Grazie per la risposta. Ma l'ho aggiunto e sembra che dealloc sia sempre chiamato nel thread secondario. Ancora non riesco a capire il motivo. – Tao

+0

L'annullamento delle operazioni in background è il modo "corretto" per farlo. Il motivo per cui non risolve il problema è dovuto al fatto che 'cancelAllOperations' non interrompe automaticamente le operazioni già in esecuzione. Quello che devi fare è nel tuo metodo 'load', dopo il thread sleep, controlla la proprietà' isCancelled' dell'operazione; in tal caso, torna senza chiamare 'done'. Ma se tutto quello che stai facendo è semplicemente mettere in pausa in un thread in background, un modo più semplice per farlo è con 'performSelector: withObject: afterDelay:'. –

0

Non sono sicuro di quello che sta succedendo in base al codice, ma sembra che viewDidAppear è sempre chiamato e creando il secondo thread, e poi si naviga lontano dal controller e rilasciarlo , e quindi il secondo thread termina e chiama performSelectorOnMainThread sull'oggetto rilasciato "self". Penso che potresti dover controllare che il rilascio non sia successo?

Il messaggio di errore che si sta verificando implica che si sta eseguendo un codice UIKit dal secondo thread. Apple ha recentemente aggiunto alcuni controlli per le chiamate con thread a UIKit, e penso che probabilmente hai solo bisogno di rifattorizzare la tua funzione di caricamento per aggiornare l'interfaccia utente sul thread principale, invece di chiamare le funzioni di UIWebView dal secondo thread.

Spero che questo aiuti!

1

Attualmente ho un problema simile nella mia applicazione. Un controller di visualizzazione che visualizza un UIWebView viene inviato a un controller di navigazione e avvia un thread in background per recuperare i dati. Se si preme il pulsante Indietro prima del termine del thread, l'applicazione si arresta in modo anomalo con lo stesso messaggio di errore.

Il problema sembra essere che NSThread mantiene il bersaglio (auto) e l'oggetto (argomento) e lo rilascia dopo che il metodo è stato eseguito - purtroppo rilascia sia dall'interno del thread. Quindi, quando viene creato il controller, il conteggio dei ritiri è 1, quando il thread viene avviato, il controller ottiene un conteggio dei ritardi pari a 2. Quando si fa scoppiare il controller prima che il thread sia terminato, il controller di navigazione rilascia il controller, il che si traduce in un mantenere il conteggio di 1. Finora questo va bene - Ma se il thread termina, NSThread rilascia il controller, che si traduce in un conteggio di conservazione di 0 e un dealloc immediato all'interno del thread. Ciò rende UIWebView (che viene rilasciato nel metodo dealloc del controller) che genera un'eccezione e un arresto anomalo dell'avviso thread.

Ho lavorato con successo a questo problema utilizzando [[self retain] autorelease] come ultima istruzione nella discussione (appena prima che il thread rilasciasse il suo pool). Ciò garantisce che l'oggetto controller non venga rilasciato immediatamente ma contrassegnato come autoreleased e deallocato successivamente nel ciclo di esecuzione del thread principale. Tuttavia questo è un trucco un po 'sporco e preferirei piuttosto trovare una soluzione migliore.

+0

La soluzione "work around" funziona per me, grazie. BTW, Tao, qual è la tua ultima decisione su quale metodo usi? – RoundOutTooSoon

+1

in realtà stai perdendo memoria ... !! – Ricibald

0

Ho provato entrambe le soluzioni pubblicate sopra, [operationQueue cancelAllOperations] e [[self retain] autorelease]. Tuttavia, con un clic rapido, ci sono ancora casi in cui il conteggio dei ritiri cade a 0 e la classe viene deallocata sul thread secondario. Al fine di evitare un incidente, ho messo il seguente nel mio dealloc per ora:

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

che è una perdita evidente, ma sembra essere il minore dei mali 2.

Eventuali informazioni aggiuntive da chiunque incontri questo problema sono le benvenute.

1

ho provato:

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

che sembra funzionare ancora meglio.

12

Ecco il codice per eseguire gli elementi UIKit sul thread principale. Se stai lavorando su un thread diverso e hai bisogno di eseguire un pezzo di codice UIKit, inseriscilo tra parentesi di questo snippet di spedizione Grand Central.

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

quando inserisci il codice, è preferibile aggiungere una breve spiegazione – ronalchn