Erhalten Sie den Fehler "Diese Anwendung ändert die Autolayout-Engine aus einem Hintergrund-Thread"?

310

Dieser Fehler ist in meinem OS X häufig mit swift aufgetreten:

"Diese Anwendung ändert die Autolayout-Engine von einem Hintergrund-Thread aus, was zu einer Beschädigung der Engine und seltsamen Abstürzen führen kann. Dies wird in einer zukünftigen Version eine Ausnahme verursachen."

Ich habe ein NSWindow und tausche die Ansichten zum contentViewFenster aus. Ich erhalte die Fehlermeldung, wenn ich versuche, eine NSApp.beginSheetim Fenster auszuführen, oder wenn ich eine subviewzum Fenster hinzufüge . Ich habe versucht, die automatische Größenänderung zu deaktivieren, und ich habe nichts, was das automatische Layout verwendet. Irgendwelche Gedanken?

Manchmal ist es in Ordnung und nichts passiert, manchmal bricht es mich total UIund nichts wird geladen

Kennzeichen
quelle
2
Aus irgendeinem Grund wurde eine ausgezeichnete Antwort unten gelöscht: github.com/nrbrook/NBUIKitMainThreadGuard
Fattie
Hat mir wenigstens ein paar Stunden gespart. Danke @Fattie
oyalhi
richtig @oyalhi. Sei vorsichtig damit, ich habe es wirklich genossen, hatte aber auch andere Probleme - es ist ein hartes Feld! ich hoffe es hilft!
Fattie
eine halb verwandte Frage
Honig

Antworten:

638

Es muss in einem anderen Thread platziert werden, damit die Benutzeroberfläche aktualisiert werden kann, sobald die Ausführung der Thread-Funktion abgeschlossen ist:

Modern Swift:

DispatchQueue.main.async {
    // Update UI
}

Ältere Versionen von Swift vor Swift 3.

dispatch_async(dispatch_get_main_queue(){
    // code here
})

Ziel c:

dispatch_async(dispatch_get_main_queue(), ^{
    // code here
});
Kennzeichen
quelle
3
Damit dies in Ziel C funktioniert, setzen Sie ^ (void) vor das {im Codeblock und ein Semikolon nach den Wörtern.
Avance

4
Während es nicht weh tut, ist ^ (nichtig) statt nur ^ nicht notwendig. Die Objective-C-Version der Antwort ist in Ordnung.
Keller

2
Es funktioniert gut für mich. Für mein Problem ist, machen Sie eine Netzwerkanforderung, und innerhalb des Erfolgsabschlussblocks habe ich Funktion aufgerufen, um die Benutzeroberfläche zu aktualisieren. Da UIKit nicht threadsicher ist, müssen Sie zum Aktualisieren der Benutzeroberfläche zum Hauptthread zurückkehren.
Rachel

1
Siehe Antwort von @Naishta für Swift 3-Lösung
Nathaniel

1
Für Swift 3: DispatchQueue.main.async () { code }, wie @Naishta sagte
smukamuka
146

Beim Debuggen mit print-Anweisungen ohne Verwendung von 'dispatch_async' wird eine ähnliche Fehlermeldung angezeigt. Wenn Sie diese Fehlermeldung erhalten, ist es an der Zeit, sie zu verwenden

Swift 4

DispatchQueue.main.async { //code }

Swift 3

DispatchQueue.main.async(){ //code }

Frühere Swift-Versionen

dispatch_async(dispatch_get_main_queue()){ //code }
Naishta
quelle
5
Da die Syntax falsch ist, sollte es sein: dispatch_async(dispatch_get_main_queue(), ^{ /* UI related code */ });Bearbeiten: Ich habe seine Antwort aktualisiert, die Syntaxformatierung funktioniert dort besser.
Zoltán
1
oder, wenn Sie sich in einem Closure befinden, wechseln Sie vom Hintergrund-Thread aus mit dem folgenden Thread: self.performSelectorOnMainThread (Selector ("yourFunction:"), withObject: 'yourArray / yourObject', waitUntilDone: true)
Naishta
1
Nein, ist es nicht, werfen Sie einen Blick auf die Syntax
Naishta
82

Der Fehler "Diese Anwendung ändert die Autolayout-Engine von einem Hintergrund-Thread aus" wird lange nach dem eigentlichen Problem in der Konsole protokolliert. Daher kann das Debuggen ohne Verwendung eines Haltepunkts schwierig sein.

Ich habe die Antwort von @ markussvensson verwendet, um mein Problem zu erkennen, und habe sie mithilfe dieses symbolischen Haltepunkts gefunden (Debug> Haltepunkte> Symbolischen Haltepunkt erstellen):

  1. Symbole: [UIView layoutIfNeeded]oder[UIView updateConstraintsIfNeeded]
  2. Bedingung: !(BOOL)[NSThread isMainThread]

Geben Sie hier die Bildbeschreibung ein

Erstellen und führen Sie die App auf dem Emulator aus und replizieren Sie die Schritte, die dazu führen, dass die Fehlermeldung ausgegeben wird (die App ist langsamer als gewöhnlich!). Xcode stoppt dann die App und markiert die Codezeile (z. B. den Aufruf einer Funktion), die über einen Hintergrund-Thread auf die Benutzeroberfläche zugreift.

k06a
quelle
3
Hmm. Ich habe das positiv bewertet, weil es sinnvoll schien. Die Ausführung wird jedoch nicht unterbrochen und ich erhalte immer noch den Fehler in meinen Protokollen. Gibt es andere Bedingungen, unter denen ich einen symbolischen Haltepunkt festlegen könnte?
Sjakelien
In meinem Fall bricht es. Die Stapelverfolgung gibt mir jedoch keinen Hinweis darauf, welche Ansicht verantwortlich ist. Und ich weiß nicht, wie ich den gezeigten Assembler-Code interpretieren sollmovq 0x10880ba(%rip), %rsi ; "_wantsReapplicationOfAutoLayoutWithLayoutDirtyOnEntry:"
Reinhard Männer
Wird die App beim Debuggen nicht ernsthaft langsam ausgeführt?
Itachi
@ Itachi yep. Ich weiß nicht, wie ich beschleunigen soll.
k06a
2
Ab Xcode 9 ist dies eine integrierte Funktion.
Stellen Sie
24

Wenn Sie versuchen, einen Textfeldwert zu aktualisieren oder eine Unteransicht in einem Hintergrundthread hinzuzufügen, kann dieses Problem auftreten. Aus diesem Grund sollten Sie diese Art von Code in den Hauptthread einfügen.

Sie müssen Methoden, die UI-Updates aufrufen, mit dispatch_asynch umschließen, um die Hauptwarteschlange abzurufen. Zum Beispiel:

dispatch_async(dispatch_get_main_queue(), { () -> Void in
   self.friendLabel.text = "You are following \(friendCount) accounts"
})

EDITIERT - SWIFT 3:

Jetzt können wir das mit dem nächsten Code tun:

// Move to a background thread to do some long running work
DispatchQueue.global(qos: .userInitiated).async {
   // Do long running task here
   // Bounce back to the main thread to update the UI
   DispatchQueue.main.async {
      self.friendLabel.text = "You are following \(friendCount) accounts"
   }
}
Jorge Luis Jiménez
quelle
23

Für mich stammt diese Fehlermeldung von einem Banner aus dem Admob SDK.

Ich konnte den Ursprung zu "WebThread" verfolgen, indem ich einen bedingten Haltepunkt festlegte.

Bedingter Haltepunkt, um herauszufinden, wer die Benutzeroberfläche aus dem Hintergrundthread aktualisiert

Dann konnte ich das Problem beseitigen, indem ich die Bannererstellung mit folgenden Informationen einkapselte:

dispatch_async(dispatch_get_main_queue(), ^{
   _bannerForTableFooter = [[GADBannerView alloc] initWithAdSize:kGADAdSizeSmartBannerPortrait];
   ...
}

Ich weiß nicht, warum dies geholfen hat, da ich nicht sehen kann, wie dieser Code von einem Nicht-Haupt-Thread aufgerufen wurde.

Hoffe es kann jedem helfen.

Markussvensson
quelle
1
Ich hatte das gleiche Problem. Ich war verwirrt, warum die Ausnahme in einem WebThread auftrat. Ich habe die gleiche Änderung vorgenommen wie Sie und es funktioniert jetzt. Ich verwende eine etwas veraltete Version des Admob SDK. Ich frage mich, ob es in der neuesten Version korrigiert wurde. Danke dafür. Ich glaube nicht, dass ich es gefunden hätte.
Larry
1
Das Update auf die neueste Version von AdMob löste dieses Problem für mich
JH95
Ich bekomme eine Pause an einem symbolischen Haltepunkt wie diesem, aber es wird kein Code angezeigt. :(
Victor Engel
20

Ich hatte dieses Problem seit dem Update auf das iOS 9 SDK, als ich einen Block aufrief, der UI-Updates in einem asynchronen NSURLConnection-Anforderungsabschluss-Handler durchführte. Das Problem wurde behoben, indem der Blockaufruf mit dispatch_main_queue in dispatch_async eingefügt wurde.

In iOS 8 hat es gut funktioniert.

Schwammfick
quelle
10

Hatte das gleiche Problem, weil ich verwendet habe performSelectorInBackground.

Bobby
quelle
Nein, ich musste Dinge im Hintergrund tun. Ich habe den NSNotificationCenter-Aufruf in die dispatch_async (dispatch_get_main_queue () -Methode eingefügt und es hat funktioniert.
Bobby
Ich habe Daten von einem URLSessionDelegate erhalten, das dann als NSNotification bezeichnet wurde. Ein UIViewController hat auf die Benachrichtigung geantwortet und dort DispatchQueue.main.async verwendet, um dem Benutzer anzuzeigen, ob die Daten gut waren oder nicht. Falsch! - Die Lösung besteht darin, die Benachrichtigung in die Hauptwarteschlange zu stellen. DispatchQueue.main.async {NotificationCenter.default.post (Name: NSNotification.Name (rawValue: networkNotificationNames.products.rawValue), Objekt: self, userInfo: [networkNotificationNames.products.rawValue: productList])}
iCyberPaul
7

Sie dürfen die Benutzeroberfläche nicht außerhalb des Hauptthreads ändern! UIKit ist nicht threadsicher, so dass das obige Problem und auch einige andere seltsame Probleme auftreten, wenn Sie dies tun. Die App kann sogar abstürzen.

Um die UIKit-Operationen auszuführen, müssen Sie den Block definieren und ihn in der Hauptwarteschlange ausführen lassen: z.

NSOperationQueue.mainQueue().addOperationWithBlock {

}
Chowdhury Md Rajib Sarwar
quelle
Dies ist in Xcode 7.2 und iOS 9.2 nicht verfügbar. Irgendeine andere Alternative?
Jayprakash Dubey
7

Offensichtlich führen Sie ein UI-Update für den Hintergrundthread durch. Kann nicht genau vorhersagen, wo, ohne Ihren Code zu sehen.

Dies sind einige Situationen, in denen dies passieren kann:

Möglicherweise machen Sie etwas im Hintergrund-Thread und verwenden es nicht. In derselben Funktion ist dieser Code leichter zu erkennen.

DispatchQueue.main.async { // do UI update here }

Aufrufen einer Funktion, die einen Webanforderungsaufruf für den Hintergrundthread ausführt, und dessen Vervollständigungshandler, der eine andere Funktion aufruft, die ein UI-Update ausführt. Um dieses Problem zu lösen, überprüfen Sie den Code, in dem Sie die Benutzeroberfläche nach dem Webrequest-Aufruf aktualisiert haben.

// Do something on background thread
DispatchQueue.global(qos: .userInitiated).async {
   // update UI on main thread
   DispatchQueue.main.async {
                // Updating whole table view
                self.myTableview.reloadData()
            }
}
Ashish Pisey
quelle
5

Das Hauptproblem bei "Diese Anwendung ändert die Autolayout-Engine aus einem Hintergrund-Thread" besteht darin, dass sie anscheinend lange nach dem eigentlichen Problem protokolliert wird. Dies kann die Fehlerbehebung sehr schwierig machen.

Ich habe es geschafft, das Problem zu lösen, indem ich drei symbolische Haltepunkte erstellt habe.

Debug> Haltepunkte> Symbolischen Haltepunkt erstellen ...

Haltepunkt 1:

  • Symbol: -[UIView setNeedsLayout]

  • Bedingung: !(BOOL)[NSThread isMainThread]

Haltepunkt 2:

  • Symbol: -[UIView layoutIfNeeded]

  • Bedingung: !(BOOL)[NSThread isMainThread]

Haltepunkt 3:

  • Symbol: -[UIView updateConstraintsIfNeeded]

  • Bedingung: !(BOOL)[NSThread isMainThread]

Mit diesen Haltepunkten können Sie leicht eine Unterbrechung in der tatsächlichen Zeile erhalten, in der Sie UI-Methoden für Nicht-Hauptthread falsch aufrufen.

www.jensolsson.se
quelle
4

Ich hatte dieses Problem beim erneuten Laden von Daten in UITableView. Durch einfaches Versenden des Nachladens wie folgt wurde das Problem für mich behoben.

    dispatch_async(dispatch_get_main_queue(), { () -> Void in
        self.tableView.reloadData()
    })
ReshDev
quelle
Das war es für mich! Ich hatte alles andere in der Hauptwarteschlange als ein streunendes reloadData ()!
Oprimus
3

Ich hatte das gleiche Problem. Es stellte sich heraus, UIAlertsdass ich die Hauptwarteschlange benötigte. Aber sie sind veraltet .
Als ich das UIAlertsauf das änderte UIAlertController, hatte ich das Problem nicht mehr und musste keinen dispatch_asyncCode verwenden. Die Lektion - achten Sie auf Warnungen. Sie helfen auch dann, wenn Sie es nicht erwarten.

epaus
quelle
3

Sie haben bereits die richtige Code-Antwort von @Mark, aber nur um meine Ergebnisse zu teilen: Das Problem ist, dass Sie eine Änderung in der Ansicht anfordern und davon ausgehen, dass dies sofort geschehen wird. In der Realität hängt das Laden einer Ansicht von den verfügbaren Ressourcen ab. Wenn alles schnell genug geladen wird und es keine Verzögerungen gibt, bemerken Sie nichts. In Szenarien, in denen es aufgrund des ausgelasteten Prozessthreads usw. zu Verzögerungen kommt, gerät die Anwendung in eine Situation, in der etwas angezeigt werden soll, obwohl es noch nicht bereit ist. Daher ist es ratsam, diese Anforderungen in asynchronen Warteschlangen zu versenden, damit sie basierend auf der Last ausgeführt werden.

Mukund Agarwal
quelle
2

Ich hatte dieses Problem, als ich TouchID verwendete. Wenn dies jemand anderem hilft, verpacken Sie Ihre Erfolgslogik, die wahrscheinlich etwas mit der Benutzeroberfläche in der Hauptwarteschlange zu tun hat.

Stuart P.
quelle
2

Dies kann so einfach sein wie das Festlegen eines Textfeld- / Beschriftungswerts oder das Hinzufügen einer Unteransicht innerhalb eines Hintergrundthreads, wodurch sich das Layout eines Felds ändern kann. Stellen Sie sicher, dass alles, was Sie mit der Schnittstelle tun, nur im Hauptthread geschieht.

Überprüfen Sie diesen Link: https://forums.developer.apple.com/thread/7399

Kiran P Nair
quelle
2

Ich hatte das gleiche Problem beim Versuch, die Fehlermeldung in UILabel im selben ViewController zu aktualisieren (es dauert eine Weile, bis die Daten aktualisiert sind, wenn ich dies mit normaler Codierung versuche). Ich habe DispatchQueuein Swift 3 Xcode 8 verwendet und es funktioniert.

FN90
quelle
2

Wenn Sie diesen Fehler beheben möchten, aktivieren Sie das Kontrollkästchen Hauptthread-Prüfer bei Problemen. Es ist meistens einfach, das Problem zu beheben, indem die problematische Zeile in der Hauptwarteschlange versandt wird.

Geben Sie hier die Bildbeschreibung ein

rockdaswift
quelle
1
Was macht das genau? "Main Thread Checker" war standardmäßig aktiviert und ich habe zusätzlich "Thread Sanitizer" und "Pause bei Problemen" für beide aktiviert, aber es wird immer noch nur die Meldung "Ändern von einem Hintergrund-Thread" ausgegeben, ohne dass weitere Informationen hinzugefügt werden.
Neph
Die Ausführung der App wird an dem Punkt angehalten, an dem die Benutzeroberfläche in einem Hintergrundthread geändert wird.
Rockdaswift
Seltsam, das hat es in meiner App (Xcode 10.2.1) nicht gemacht. Ich musste manuell Haltepunkte hinzufügen (wie hier beschrieben ), damit es pausierte und mich auf die Codezeile zeigte.
Neph
Und was mache ich, um diese Optionen zu sehen?
David Rector
Bearbeiten Sie das Schema Ihres Ziels
Rockdaswift
1

Für mich war das Problem das Folgende. Stellen Sie sicher, dass performSegueWithIdentifier:der Haupt-Thread ausgeführt wird:

dispatch_async (dispatch_get_main_queue(), ^{
  [self performSegueWithIdentifier:@"ViewController" sender:nil];
});
Offek
quelle
1

Swift 4,

Angenommen, Sie rufen eine Methode mithilfe der Operationswarteschlange auf

operationQueue.addOperation({
            self.searchFavourites()
        })

Angenommen, die Funktion searchFavourites lautet wie folgt:

func searchFavourites() {
     DispatchQueue.main.async {
                    //Your code
                }
}

Wenn Sie den gesamten Code in der Methode "searchFavourites" im Hauptthread aufrufen, wird immer noch ein Fehler ausgegeben, wenn Sie eine Benutzeroberfläche darin aktualisieren.

Diese Anwendung ändert die Autolayout-Engine aus einem Hintergrund-Thread, nachdem über den Haupt-Thread auf die Engine zugegriffen wurde.

Also Lösung verwenden,

operationQueue.addOperation({
            DispatchQueue.main.async {
                self.searchFavourites()
            }
        })

Für diese Art von Szenario.

Pramod Mehr
quelle
1

Hier sehen Sie sich diese Zeile aus den Protokollen an

$S12AppName18ViewControllerC11Func()ySS_S2StF + 4420

Sie können überprüfen, welche Funktion vom Hintergrundthread aus aufgerufen wird oder wo Sie die API-Methode aufrufen, um Ihre Funktion vom Hauptthread aus wie folgt aufzurufen.

DispatchQueue.main.async { func()}

func () ist die Funktion, die Sie als Ergebnis eines erfolgreichen API-Aufrufs aufrufen möchten.

Loggt hier

This application is modifying the autolayout engine from a background thread after the engine was accessed from the main thread. This can lead to engine corruption and weird crashes.
 Stack:(
    0   Foundation                          0x00000001c570ce50 <redacted> + 96
    1   Foundation                          0x00000001c5501868 <redacted> + 32
    2   Foundation                          0x00000001c5544370 <redacted> + 540
    3   Foundation                          0x00000001c5543840 <redacted> + 396
    4   Foundation                          0x00000001c554358c <redacted> + 272
    5   Foundation                          0x00000001c5542e10 <redacted> + 264
    6   UIKitCore                           0x00000001f20d62e4 <redacted> + 488
    7   UIKitCore                           0x00000001f20d67b0 <redacted> + 36
    8   UIKitCore                           0x00000001f20d6eb0 <redacted> + 84
    9   Foundation                          0x00000001c571d124 <redacted> + 76
    10  Foundation                          0x00000001c54ff30c <redacted> + 108
    11  Foundation                          0x00000001c54fe304 <redacted> + 328
    12  UIKitCore                           0x00000001f151dc0c <redacted> + 156
    13  UIKitCore                           0x00000001f151e0c0 <redacted> + 152
    14  UIKitCore                           0x00000001f1514834 <redacted> + 868
    15  UIKitCore                           0x00000001f1518760 <redacted> + 104
    16  UIKitCore                           0x00000001f1543370 <redacted> + 1772
    17  UIKitCore                           0x00000001f1546598 <redacted> + 120
    18  UIKitCore                           0x00000001f14fc850 <redacted> + 1452
    19  UIKitCore                           0x00000001f168f318 <redacted> + 196
    20  UIKitCore                           0x00000001f168d330 <redacted> + 144
    21  AppName                        0x0000000100b8ed00 $S12AppName18ViewControllerC11Func()ySS_S2StF + 4420
    22  AppName                        0x0000000100b8d9f4 $S12CcfU0_y10Foundation4DataVSg_So13NSURLResponseCSgs5Error_pSgtcfU_ + 2384
    23  App NAme                        0x0000000100a98f3c $S10Foundation4DataVSgSo13NSURLResponseCSgs5Error_pSgIegggg_So6NSDataCSgAGSo7NSErrorCSgIeyByyy_TR + 316
    24  CFNetwork                           0x00000001c513aa00 <redacted> + 32
    25  CFNetwork                           0x00000001c514f1a0 <redacted> + 176
    26  Foundation                          0x00000001c55ed8bc <redacted> + 16
    27  Foundation                          0x00000001c54f5ab8 <redacted> + 72
    28  Foundation                          0x00000001c54f4f8c <redacted> + 740
    29  Foundation                          0x00000001c55ef790 <redacted> + 272
    30  libdispatch.dylib                   0x000000010286f824 _dispatch_call_block_and_release + 24
    31  libdispatch.dylib                   0x0000000102870dc8 _dispatch_client_callout + 16
    32  libdispatch.dylib                   0x00000001028741c4 _dispatch_continuation_pop + 528
    33  libdispatch.dylib                   0x0000000102873604 _dispatch_async_redirect_invoke + 632
    34  libdispatch.dylib                   0x00000001028821dc _dispatch_root_queue_drain + 376
    35  libdispatch.dylib                   0x0000000102882bc8 _dispatch_worker_thread2 + 156
    36  libsystem_pthread.dylib             0x00000001c477917c _pthread_wqthread + 472
    37  libsystem_pthread.dylib             0x00000001c477bcec start_wqthread + 4
)
Govind Wadhwa
quelle
0

Ich bin auch auf dieses Problem gestoßen, als eine Menge dieser Nachrichten und Stapelspuren in der Ausgabe gedruckt wurden, als ich die Größe des Fensters auf eine kleinere Größe als den Anfangswert geändert habe. Ich verbrachte eine lange Zeit damit, das Problem herauszufinden, und dachte, ich würde die ziemlich einfache Lösung teilen. Ich hatte einmal Can Draw Concurrentlyauf einem NSTextViewdurch IB aktiviert . Dadurch wird AppKit mitgeteilt, dass die Ansichtsmethode draw(_:)von einem anderen Thread aus aufgerufen werden kann . Nach dem Deaktivieren wurden keine Fehlermeldungen mehr angezeigt. Vor dem Update auf macOS 10.14 Beta gab es keine Probleme, aber gleichzeitig habe ich auch begonnen, den Code zu ändern, um die Arbeit mit der Textansicht auszuführen.

Andreas
quelle