Obtenir une erreur «Cette application modifie le moteur de mise en page automatique à partir d'un thread d'arrière-plan»?

310

J'ai souvent rencontré cette erreur dans mon OS X en utilisant swift:

"Cette application modifie le moteur de mise en page automatique à partir d'un thread d'arrière-plan, ce qui peut entraîner une corruption du moteur et des plantages étranges. Cela entraînera une exception dans une prochaine version."

J'ai ma fenêtre NSWindow et j'échange les vues vers contentViewla fenêtre. J'obtiens l' erreur lorsque j'essaie de faire un NSApp.beginSheetsur la fenêtre ou lorsque j'ajoute un subviewà la fenêtre. J'ai essayé de désactiver le redimensionnement automatique, et je n'ai rien en utilisant la mise en page automatique. Des pensées?

Parfois ça va et rien ne se passe, d'autres fois ça me casse totalement UIet rien ne se charge

marque
la source
2
Pour une raison quelconque, une excellente réponse ci-dessous a été supprimée: github.com/nrbrook/NBUIKitMainThreadGuard
Fattie
Cela m'a fait gagner quelques heures au moins. Merci @Fattie
oyalhi
à droite @oyalhi. soyez prudent en l'utilisant, j'ai vraiment apprécié mais j'ai eu aussi d'autres problèmes - c'est un domaine difficile! J'espère que ça aide!
Fattie
une question semi connexe
Honey

Réponses:

638

Il doit être placé dans un thread différent qui permet à l'interface utilisateur de se mettre à jour dès que l'exécution de la fonction de thread est terminée:

Swift moderne:

DispatchQueue.main.async {
    // Update UI
}

Versions plus anciennes de Swift, avant Swift 3.

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

Objectif c:

dispatch_async(dispatch_get_main_queue(), ^{
    // code here
});
marque
la source
3
Pour que cela fonctionne dans l'Objectif C, mettez ^ (void) avant le {dans le bloc de code et un post-point virgule.
avance

4
Bien que cela ne fasse pas mal, ^ (vide) au lieu de simplement ^ n'est pas nécessaire. La version Objective-C de la réponse est très bien.
Keller

2
Ça fonctionne bien pour moi. Pour mon problème, faites une demande réseau et à l'intérieur du bloc de réussite, j'ai appelé la fonction pour mettre à jour l'interface utilisateur. Étant donné qu'UIKit n'est pas sûr pour les threads, vous devez renvoyer le thread principal pour mettre à jour l'interface utilisateur.
Rachel

1
Voir la réponse de @Naishta pour la solution Swift 3
Nathaniel

1
Pour Swift 3: DispatchQueue.main.async () { code }, comme @Naishta l'a dit
smukamuka
146

Vous obtenez un message d'erreur similaire lors du débogage avec des instructions d'impression sans utiliser «dispatch_async». Ainsi, lorsque vous obtenez ce message d'erreur, il est temps d'utiliser

Swift 4

DispatchQueue.main.async { //code }

Swift 3

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

Versions Swift antérieures

dispatch_async(dispatch_get_main_queue()){ //code }
Naishta
la source
5
parce que la syntaxe est erronée, devrait être: dispatch_async(dispatch_get_main_queue(), ^{ /* UI related code */ });Edit: J'ai mis à jour sa réponse, le formatage de la syntaxe fonctionne mieux là-bas.
Zoltán
1
ou, à l'intérieur d'une fermeture, accédez au fil principal à partir du fil d'arrière-plan en utilisant ceci: self.performSelectorOnMainThread (Selector ("yourFunction:"), withObject: 'yourArray / yourObject', waitUntilDone: true)
Naishta
1
Non, ce n'est pas le cas, jetez un œil à la syntaxe
Naishta
82

L'erreur "cette application modifie le moteur de mise en page automatique à partir d'un thread d'arrière-plan" est enregistrée dans la console longtemps après le problème réel, donc le débogage peut être difficile sans utiliser de point d'arrêt.

J'ai utilisé la réponse de @ markussvensson pour détecter mon problème et je l'ai trouvé en utilisant ce point d' arrêt symbolique (Débogage> Points d'arrêt> Créer un point d'arrêt symbolique):

  1. Symboles: [UIView layoutIfNeeded]ou[UIView updateConstraintsIfNeeded]
  2. État: !(BOOL)[NSThread isMainThread]

entrez la description de l'image ici

Générez et exécutez l'application sur l'émulateur et reproduisez les étapes qui mènent au message d'erreur (l'application sera plus lente que d'habitude!). Xcode arrêtera alors l'application et marquera la ligne de code (par exemple l'appel d'un func) qui accède à l'interface utilisateur à partir d'un thread d'arrière-plan.

k06a
la source
3
Hmm. J'ai voté en faveur de cela, car cela semblait logique. Cependant, l'exécution ne s'arrête pas et j'obtiens toujours l'erreur dans mes journaux. Y a-t-il d'autres conditions que je pourrais utiliser pour définir un point d'arrêt symbolique?
Sjakelien
Dans mon cas, ça casse. Cependant, la trace de la pile ne me donne aucune indication quant à la vue responsable. Et je ne sais pas comment interpréter le code assembleur montrémovq 0x10880ba(%rip), %rsi ; "_wantsReapplicationOfAutoLayoutWithLayoutDirtyOnEntry:"
Reinhard Männer
Cela ne ralentira-t-il pas sérieusement l'application lors du débogage?
Itachi
@Itachi yep. Je ne sais pas comment accélérer.
k06a
2
À partir de Xcode 9, c'est une fonctionnalité intégrée. Assurez-vous simplement que l'option "Main Thread Checker" est activée dans l'onglet "Diagnostics" des paramètres du schéma
AndrewPo
24

Lorsque vous essayez de mettre à jour une valeur de champ de texte ou d'ajouter une sous-vue à l'intérieur d'un thread d'arrière-plan, vous pouvez obtenir ce problème. Pour cette raison, vous devez mettre ce type de code dans le thread principal.

Vous devez encapsuler les méthodes qui appellent les mises à jour de l'interface utilisateur avec dispatch_asynch pour obtenir la file d'attente principale. Par exemple:

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

MODIFIÉ - SWIFT 3:

Maintenant, nous pouvons le faire en suivant le code suivant:

// 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
la source
23

Pour moi, ce message d'erreur provient d'une bannière d'Admob SDK.

J'ai pu suivre l'origine de "WebThread" en définissant un point d'arrêt conditionnel.

point d'arrêt conditionnel pour trouver qui met à jour l'interface utilisateur à partir du thread d'arrière-plan

Ensuite, j'ai pu me débarrasser du problème en encapsulant la création de la bannière avec:

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

Je ne sais pas pourquoi cela a aidé car je ne vois pas comment ce code a été appelé à partir d'un thread non principal.

J'espère que cela peut aider n'importe qui.

Markussvensson
la source
1
J'ai eu le même problème. J'étais confus pourquoi l'exception se produisait dans un WebThread. J'ai fait le même changement que vous et cela fonctionne maintenant. J'utilise une version légèrement obsolète de l'addob sdk. Je me demande si cela a été corrigé dans la dernière version. Merci pour cela. Je ne pense pas que je l'aurais trouvé.
Larry
1
La mise à jour vers la dernière version d'AdMob a résolu ce problème pour moi
JH95
J'obtiens une pause à un point d'arrêt symbolique comme celui-ci, mais aucun code n'est affiché. :(
Victor Engel
20

J'ai eu ce problème depuis la mise à jour vers le SDK iOS 9 lorsque j'appelais un bloc qui effectuait des mises à jour de l'interface utilisateur dans un gestionnaire de fin de demande asynchrone NSURLConnection. Mettre l'appel de bloc dans un dispatch_async à l'aide de dispatch_main_queue a résolu le problème.

Cela a bien fonctionné dans iOS 8.

spongessuck
la source
10

J'ai eu le même problème parce que j'utilisais performSelectorInBackground.

Policier
la source
Non, j'avais besoin de faire des choses en arrière-plan. J'ai mis l'appel NSNotificationCenter dans la méthode dispatch_async (dispatch_get_main_queue () et cela a fonctionné.
Bobby
J'obtenais des données d'un URLSessionDelegate, qui a ensuite appelé une NSNotification un UIViewController a répondu à la notification et là-dedans j'ai utilisé DispatchQueue.main.async pour montrer à l'utilisateur si les données étaient bonnes ou non. Faux! - La solution est de mettre la notification dans la file d'attente principale. DispatchQueue.main.async {NotificationCenter.default.post (nom: NSNotification.Name (rawValue: networkNotificationNames.products.rawValue), objet: self, userInfo: [networkNotificationNames.products.rawValue: productList])}
iCyberPaul
7

Vous ne devez pas modifier l'interface utilisateur en dehors du thread principal! UIKit n'est pas sûr pour les threads, de sorte que le problème ci-dessus et également d'autres problèmes étranges se poseront si vous faites cela. L'application peut même planter.

Donc, pour faire les opérations UIKit, vous devez définir le bloc et le laisser être exécuté sur la file d'attente principale: comme,

NSOperationQueue.mainQueue().addOperationWithBlock {

}
Chowdhury Md Rajib Sarwar
la source
Ce n'est pas disponible dans Xcode 7.2 et iOS 9.2. Une autre alternative?
Jayprakash Dubey
7

De toute évidence, vous effectuez une mise à jour de l'interface utilisateur sur le fil de fond. Impossible de prédire exactement où, sans voir votre code.

Ce sont des situations qui peuvent arriver: -

vous faites peut-être quelque chose sur le fil d'arrière-plan et ne l'utilisez pas. Étant dans la même fonction, ce code est plus facile à repérer.

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

appelant un func faisant un appel de requête Web sur le thread d'arrière-plan et son gestionnaire d'achèvement appelant un autre func faisant une mise à jour de l'interface utilisateur. pour résoudre ce problème, essayez de vérifier le code dans lequel vous avez mis à jour l'interface utilisateur après l'appel de la demande de Webrequest.

// 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
la source
5

Le problème principal avec "Cette application modifie le moteur de mise en page automatique à partir d'un thread d'arrière-plan" est qu'il semble être enregistré longtemps après que le problème se soit produit, ce qui peut rendre le dépannage très difficile.

J'ai réussi à résoudre le problème en créant trois points d'arrêt symboliques.

Déboguer> Points d'arrêt> Créer un point d'arrêt symbolique ...

Point d'arrêt 1:

  • Symbole: -[UIView setNeedsLayout]

  • État: !(BOOL)[NSThread isMainThread]

Point d'arrêt 2:

  • Symbole: -[UIView layoutIfNeeded]

  • État: !(BOOL)[NSThread isMainThread]

Point d'arrêt 3:

  • Symbole: -[UIView updateConstraintsIfNeeded]

  • État: !(BOOL)[NSThread isMainThread]

Avec ces points d'arrêt, vous pouvez facilement obtenir une pause sur la ligne réelle où vous appelez incorrectement des méthodes d'interface utilisateur sur un thread non principal.

www.jensolsson.se
la source
4

J'ai eu ce problème lors du rechargement des données dans UITableView. L'envoi de rechargement comme suit a résolu le problème pour moi.

    dispatch_async(dispatch_get_main_queue(), { () -> Void in
        self.tableView.reloadData()
    })
ReshDev
la source
C'était tout pour moi! J'avais tout le reste dans la file d'attente principale, mais un reloadData () errant!
Oprimus
3

J'ai eu le même problème. Il s'avère que j'utilisais UIAlertsqui avait besoin de la file d'attente principale. Mais, ils ont été dépréciés .
Lorsque j'ai changé le UIAlertsen UIAlertController, je n'ai plus eu le problème et je n'ai plus eu à utiliser de dispatch_asynccode. La leçon - faites attention aux avertissements. Ils aident même lorsque vous ne vous y attendez pas.

epaus
la source
3

Vous avez déjà la bonne réponse de code de @Mark mais, juste pour partager mes conclusions: Le problème est que vous demandez un changement dans la vue et supposez que cela se produira instantanément. En réalité, le chargement d'une vue dépend des ressources disponibles. Si tout se charge assez rapidement et qu'il n'y a pas de retard, vous ne remarquez rien. Dans les scénarios, où il y a un retard dû au processus occupé, etc., l'application se retrouve dans une situation où elle est censée afficher quelque chose même si elle n'est pas encore prête. Par conséquent, il est conseillé de répartir ces demandes dans des files d'attente asynchrones afin qu'elles soient exécutées en fonction de la charge.

Mukund Agarwal
la source
2

J'ai eu ce problème lorsque j'utilisais TouchID si cela aide quelqu'un d'autre, encapsulez votre logique de réussite qui fait probablement quelque chose avec l'interface utilisateur dans la file d'attente principale.

Stuart P.
la source
2

Cela peut être aussi simple que de définir une valeur de champ / étiquette de texte ou d'ajouter une sous-vue à l'intérieur d'un fil d'arrière-plan, ce qui peut entraîner la modification de la disposition d'un champ. Assurez-vous que tout ce que vous faites avec l'interface ne se produit que dans le thread principal.

Vérifiez ce lien: https://forums.developer.apple.com/thread/7399

Kiran P Nair
la source
2

J'ai eu le même problème lors de la tentative de mise à jour du message d'erreur dans UILabel dans le même ViewController (cela prend un peu de temps pour mettre à jour les données lorsque vous essayez de le faire avec un codage normal). J'ai utilisé DispatchQueuedans Swift 3 Xcode 8 et cela fonctionne.

FN90
la source
2

Si vous souhaitez rechercher cette erreur, utilisez la case à cocher principale du vérificateur de threads en cas de problème. La réparation est facile la plupart du temps, en répartissant la ligne problématique dans la file d'attente principale.

entrez la description de l'image ici

rockdaswift
la source
1
Qu'est-ce que cela fait exactement? "Main Thread Checker" a été activé par défaut et j'ai également vérifié "Thread Sanitizer" et "Pause on issues" pour les deux, mais il ne renvoie que le message "modification à partir d'un fil d'arrière-plan" sans y ajouter plus d'informations.
Neph
Il suspend l'exécution de l'application au point où l'interface utilisateur est modifiée dans un thread d'arrière-plan.
rockdaswift
Bizarre, cela n'a pas été fait dans mon application (Xcode 10.2.1). J'ai dû ajouter manuellement des points d'arrêt (comme décrit ici ) pour le mettre en pause et me diriger vers la ligne de code.
Néph
Et que dois-je faire pour voir cet ensemble d'options?
David Rector
Modifiez le schéma de votre cible
rockdaswift
1

Pour moi, le problème était le suivant. Assurez-vous que performSegueWithIdentifier:c'est effectué sur le thread principal:

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

Swift 4,

Supposons que si vous appelez une méthode à l'aide d'une file d'attente d'opérations

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

Et supposons que la fonction searchFavourites est comme,

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

si vous appelez, tout le code à l'intérieur de la méthode "searchFavourites" sur le thread principal, il donnera toujours une erreur si vous mettez à jour une interface utilisateur.

Cette application modifie le moteur de mise en page automatique à partir d'un thread d'arrière-plan après avoir accédé au moteur à partir du thread principal.

Alors utilisez la solution,

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

Pour ce genre de scénario.

Pramod Plus
la source
1

Ici, consultez cette ligne dans les journaux

$S12AppName18ViewControllerC11Func()ySS_S2StF + 4420

vous pouvez vérifier quelle fonction appelant à partir du thread d'arrière-plan ou où vous appelez la méthode api, vous devez appeler votre fonction à partir du thread principal comme ceci.

DispatchQueue.main.async { func()}

func () est la fonction que vous souhaitez appeler à la suite d'un succès d'appel api ou autre.

Journaux ici

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
la source
0

J'ai également rencontré ce problème, voyant une tonne de ces messages et des traces de pile imprimées dans la sortie, lorsque j'ai redimensionné la fenêtre à une taille inférieure à sa valeur initiale. Après avoir longtemps réfléchi au problème, j'ai pensé partager la solution plutôt simple. J'avais une fois activé Can Draw Concurrentlysur un NSTextViewvia IB. Cela indique à AppKit qu'il peut appeler la draw(_:)méthode de la vue à partir d'un autre thread. Après l'avoir désactivé, je n'ai plus reçu de message d'erreur. Je n'ai rencontré aucun problème avant la mise à jour vers macOS 10.14 Beta, mais en même temps, j'ai également commencé à modifier le code pour effectuer un travail avec la vue texte.

Andreas
la source