2009-06-03 18 views
8

Ich habe eine UIWebView in einem Viewcontroller, der zwei Methoden wie folgt hat. Die Frage ist, ob ich diesen Controller herausspringe (auf die Navigationsleiste tippen) bevor der zweite Thread fertig ist, wird die App nach [Super Dealloc] abstürzen, weil "versucht wurde, die Websperre von einem anderen Thread als dem Hauptthread zu erhalten Der Web - Thread. Dies kann ein Ergebnis des Aufrufs von UIKit von einem sekundären Thread sein. Jede Hilfe würde wirklich geschätzt werden.UIWebView in Multithread ViewController

+0

[My Solution (verwendet eine NSTimer für die letzte Version)] (http://stackoverflow.com/questions/6353471/block-release-deallocating-ui-objects-on-a-background -thread/6482941 # 6482941 "Meine Lösung") – Jeff

Antwort

35

Ich hatte die gleiche Lösung, wo ein Hintergrund-Thread die letzte Version war, verursacht Dealloc der View-Controller in einem Hintergrund-Thread mit dem gleichen Absturz passieren passieren.

Die obige [[self retain] autorelease] würde immer noch dazu führen, dass die endgültige Veröffentlichung aus dem Autorelease-Pool des Hintergrundthreads erfolgt. (Es sei denn, es gibt etwas Besonderes an Releases aus dem Autorelease-Pool, ich bin überrascht, dass dies einen Unterschied machen würde).

fand ich meint dies als ideale Lösung, diesen Code in meine Ansicht Controller-Klasse Abdrucken:

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

Dies stellt sicher, dass die Methode meiner release View-Controller-Klasse immer auf dem Haupt-Thread ausgeführt wird.

Ich bin ein wenig überrascht, dass bestimmte Objekte, die nur korrekt vom Hauptthread dealloc'ed werden müssen, nicht schon so etwas wie dies in gebaut. Oh well ...

+0

Dies beendete eine große Kopfschmerzen für mich. Vielen Dank! – spstanley

+0

Froh, dass ich das gefunden habe - April 2011 – GuybrushThreepwood

+0

Vielen Dank !! –

1

Im Allgemeinen sollten Sie alle Hintergrundoperationen abbrechen, wenn die Ansicht, die sie verwendet, verschwindet. Wie in:

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

Vielen Dank für Ihre Antwort. Aber ich habe hinzugefügt, und es scheint, dass Dealloc immer im sekundären Thread aufgerufen wird. Kann immer noch nicht den Grund herausfinden. – Tao

+0

Das Abbrechen von Hintergrundoperationen ist der "richtige" Weg, dies zu tun. Der Grund, warum das Problem nicht gelöst wird, ist, weil 'cancelAllOperations' bereits laufende Operationen nicht automatisch abbricht. Was Sie tun müssen, ist in Ihrer 'load' Methode, nach dem Thread-Sleep, überprüfen Sie die' isCancelled' Eigenschaft der Operation; Wenn ja, kehren Sie zurück, ohne 'done' zu ​​nennen. Aber wenn Sie nur in einem Hintergrundthread pausieren, ist dies mit 'performSelector: withObject: afterDelay:' einfacher. –

0

ich bin nicht sicher genau das, was Ihr Code going basiert auf, aber es sieht aus wie viewDidAppear das zweite Thread aufgerufen wird immer und zu schaffen und dann weg Sie die Navigation von der Steuerung und Freigabe und dann beendet der zweite Thread und ruft performSelectorOnMainThread für das freigegebene Objekt "self" auf. Ich denke, Sie müssen nur überprüfen, ob die Veröffentlichung nicht stattgefunden hat?

Die Fehlermeldung, die Sie erhalten, impliziert, dass Sie etwas UIKit-Code aus Ihrem zweiten Thread ausführen. Apple hat kürzlich einige Checks für Threaded-Aufrufe an UIKit hinzugefügt, und ich denke, dass Sie wahrscheinlich nur Ihre Ladefunktion umgestalten müssen, um die Benutzeroberfläche im Hauptthread zu aktualisieren, anstatt die UIWebView-Funktionen aus dem zweiten Thread aufzurufen.

Hoffe, dass hilft!

1

Ich habe derzeit ein ähnliches Problem in meiner Anwendung. Ein View-Controller, der ein UIWebView anzeigt, wird an einen Navigationscontroller gesendet und startet einen Hintergrund-Thread, um Daten abzurufen. Wenn Sie den Zurück-Knopf drücken, bevor der Thread beendet ist, stürzt die Anwendung mit derselben Fehlermeldung ab.

Das Problem scheint zu sein, dass NSThread das Ziel (self) und Objekt (Argument) behält und es nach dem Ausführen der Methode freigibt - leider veröffentlicht es beide aus dem Thread. Wenn also der Controller erstellt wird, ist die Retain-Anzahl 1, wenn der Thread gestartet wird, erhält der Controller eine Retain-Anzahl von 2. Wenn Sie den Controller beenden, bevor der Thread fertig ist, gibt der Navigationscontroller den Controller frei behalten Sie die Zählung von 1. Bis jetzt ist das in Ordnung - Aber wenn der Thread schließlich beendet wird, gibt NSThread den Controller frei, was zu einem Retain-Zählerstand von 0 und einem sofortigen Dealloc innerhalb des Threads führt. Dies führt dazu, dass das UIWebView (das in der Dealloc-Methode des Controllers freigegeben wird) diese Threadwarnungsausnahme auslöst und abstürzt.

Ich arbeitete erfolgreich um diese mit [[self retain] autorelease] als letzte Anweisung im Thread (kurz bevor der Thread seinen Pool freigibt). Dies stellt sicher, dass das Controller-Objekt nicht sofort freigegeben wird, sondern später in der Lauf-Schleife des Haupt-Threads als automatisch freigegeben markiert und freigegeben wird. Allerdings ist dies ein etwas schmutziger Hack und ich würde lieber eine bessere Lösung finden.

+0

Ihre Workaround-Lösung funktioniert für mich, danke. BTW, Tao, was ist Ihre letzte Entscheidung, welche Methode Sie verwenden? – RoundOutTooSoon

+1

in Wirklichkeit sind Sie undicht Speicher ... !! – Ricibald

0

Ich habe beide oben genannten Lösungen versucht, [operationQueue cancelAllOperations], und [[self retain] autorelease]. Es gibt jedoch immer noch Fälle, in denen die Retain-Anzahl auf 0 fällt und die Klasse für den sekundären Thread freigegeben wird. Um einen Absturz zu vermeiden, habe ich folgendes in meinem dealloc jetzt:

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

, die eine offensichtliche Leck vorhanden ist, scheint aber das kleinere von zwei Übeln zu sein.

Jeder zusätzliche Einblick von jedem, der dieses Problem hat, ist willkommen.

1

Ich habe versucht:

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

, die noch besser zu funktionieren scheint.

12

Hier einige Code zu Führen Sie UIKit-Elemente im Hauptthread aus. Wenn Sie an einem anderen Thread arbeiten und einen UIKit-Code ausführen müssen, fügen Sie ihn einfach in die Klammern dieses Grand Central Dispatch-Snippets ein.

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

Wenn Sie Code schreiben, ist es vorzuziehen, eine kurze Erklärung hinzuzufügen – ronalchn