2009-06-03 21 views
8

Mam UIWebView w viewcontroller, który ma dwie metody, jak poniżej. Pytanie brzmi: jeśli wyskoczę (stukam w pasek nawigacyjny) tego kontrolera przed wykonaniem drugiego wątku, aplikacja ulegnie awarii po [super zwolnieniu], ponieważ "Próbowano uzyskać blokadę sieci z wątku innego niż główny wątek lub wątku internetowego. Może to być wynikiem wywołania UIKit z wątku dodatkowego. ". Każda pomoc będzie naprawdę doceniona.UIWebView w wielowątkowej ViewController

+0

[My Roztwór (wykorzystuje NSTimer w ostatnim wydaniu)] (http://stackoverflow.com/questions/6353471/block-release-deallocating-ui-objects-on-a-background -thread/6482941 # 6482941 "Moje rozwiązanie") – Jeff

Odpowiedz

35

Miałem takie samo rozwiązanie, w którym wątek tła był ostatnim wydaniem, powodując dealloc kontrolera widoku, aby wystąpił w wątku tła kończącym się z tą samą awarią.

Powyższa [[self retain] autorelease] nadal będzie powodować, że ostateczne wydanie dzieje się z puli autorelease wątku tła. (Chyba że jest coś specjalnego w wydaniach z puli autorelease, jestem zaskoczony, że to by miało znaczenie).

Znalazłem to jako mój idealne rozwiązanie, umieszczając ten kod do mojego widoku klasie kontrolera:

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

Gwarantuje to, że metoda mojej klasie widok kontrolera release jest zawsze wykonywany na głównym wątku.

Jestem trochę zaskoczony, że niektóre przedmioty, które mogą być prawidłowo dealloc'ed tylko z głównego wątku nie masz jeszcze coś takiego wbudowane w. No cóż ...

+0

To zakończyło dla mnie poważny ból głowy. Dzięki! – spstanley

+0

Cieszę się, że znalazłem to - kwiecień 2011 – GuybrushThreepwood

+0

Dzięki za tonę !! –

1

Zasadniczo należy anulować wszelkie operacje w tle, gdy widok, który ich używa, znika. Jak w:

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

Dziękuję za odpowiedź. Ale dodałem to i wydaje się, że dealloc jest zawsze wywoływany w wątku dodatkowym. Nadal nie potrafię zrozumieć przyczyny. – Tao

+0

Anulowanie operacji w tle to "właściwy" sposób na zrobienie tego. Powód, dla którego nie rozwiązuje problemu, polega na tym, że 'cancelAllOperations' nie przerywa automatycznie już działających operacji. To, co musisz zrobić, to w metodzie 'load', po snu wątku, sprawdź właściwość' isCancelled' operacji; jeśli tak, zwróć bez wywoływania 'done'. Ale jeśli wszystko, co robisz, jest po prostu pauzowaniem w wątku tła, łatwiejszy sposób to zrobić z 'performSelector: withObject: afterDelay:'. –

0

nie jestem pewien dokładnie, co się dzieje na podstawie kodu, ale wygląda na to viewDidAppear jest uzyskiwanie nazywa i tworząc drugą nitkę, a następnie jesteś przechodząc od kontrolera i zwalniając go , a następnie drugi wątek kończy i wywołuje performSelectorOnMainThread na zwolnionym obiekcie "self". Myślę, że możesz po prostu sprawdzić, czy wydanie nie nastąpiło?

Komunikat o błędzie, który otrzymujesz, oznacza, że ​​uruchamiasz jakiś kod UIKit z drugiego wątku. Apple ostatnio dodał kilka sprawdzeń dla wątkowych wywołań do UIKit, i myślę, że prawdopodobnie potrzebujesz tylko refaktoryzować swoją funkcję ładowania, aby zaktualizować interfejs na głównym wątku, zamiast wywoływać funkcje UIWebView z drugiego wątku.

Nadzieję, że pomaga!

1

Obecnie mam podobny problem w mojej aplikacji. Kontroler widoku wyświetlający interfejs UIWebView jest przesyłany do kontrolera nawigacyjnego i uruchamia wątek tła, aby pobrać dane. Jeśli naciśniesz przycisk Wstecz przed zakończeniem wątku, aplikacja ulegnie awarii z tym samym komunikatem o błędzie.

Problem polega na tym, że NSThread zachowuje cel (własny) i obiekt (argument) i zwalnia go po uruchomieniu metody - niestety uwalnia obie z wewnątrz wątku. Tak więc po utworzeniu kontrolera liczba zatrzymań wynosi 1, gdy wątek jest uruchamiany, kontroler otrzymuje wartość zatrzymania równą 2. Po naciśnięciu kontrolera przed zakończeniem wątku kontroler nawigacyjny zwalnia kontroler, co powoduje zachowaj liczbę 1. Jak na razie jest to w porządku - Ale jeśli wątek w końcu się zakończy, NSThread zwalnia kontroler, co skutkuje licznikiem zatrzymania równym 0 i natychmiastowym zwolnieniem z wątku. To sprawia, że ​​UIWebView (który jest wydany w dealloc metody kontrolera) podnieść wyjątek i awarię ostrzeżenia o wątku.

Udało mi się obejść ten problem, używając [[self retain] autorelease] jako ostatniej instrukcji w wątku (tuż przed uwolnieniem wątku przez pulę). Gwarantuje to, że obiekt kontrolera nie zostanie natychmiast zwolniony, ale zostanie oznaczony jako autoodpowiedziany i zwolniony później w pętli uruchamiania głównego wątku. Jest to jednak nieco brudny hack i wolałbym raczej znaleźć lepsze rozwiązanie.

+0

Twoje rozwiązanie "obejść" działa dla mnie, dzięki. BTW, Tao, jaka jest twoja ostatnia decyzja, której metody używasz? – RoundOutTooSoon

+1

w rzeczywistości jesteś przecieka pamięć ... !! – Ricibald

0

Próbowałem obu rozwiązań przedstawionych powyżej, [operationQueue cancelAllOperations] i [[self retain] autorelease]. Jednak za pomocą szybkiego kliknięcia nadal istnieją przypadki, w których liczba zatrzymań spada do 0, a klasa zostaje zwolniona na wątku dodatkowym. W celu uniknięcia katastrofy, kładę następujących w moim dealloc teraz:

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

co jest oczywistym wyciek, ale wydaje się być mniejsza od 2 zła.

Wszelkie dodatkowe informacje od osób, które mają ten problem, są mile widziane.

1

Próbowałem:

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

który wydaje się działać jeszcze lepiej.

12

Oto kod uruchamiać elementy UIKit w głównym wątku. Jeśli pracujesz nad innym wątkiem i musisz uruchomić fragment kodu UIKit, po prostu umieść go między nawiasami tego fragmentu 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

podczas pisania kodu najlepiej jest dodać krótkie wyjaśnienie – ronalchn

Powiązane problemy