Występuje błąd „Ta aplikacja modyfikuje silnik autolayout z wątku w tle”?

310

Często napotykałem ten błąd w moim systemie OS X za pomocą swift:

„Ta aplikacja modyfikuje silnik autolayout z wątku w tle, co może prowadzić do uszkodzenia silnika i dziwnych awarii. Spowoduje to wyjątek w przyszłej wersji”.

Mam moje okno NSWindow i zmieniam widoki contentVieww oknie. Występuje błąd, gdy próbuję zrobić NSApp.beginSheetokno w oknie lub dodać go subviewdo okna. Próbowałem wyłączyć rzeczy z automatyczną zmianą rozmiaru i nie mam nic przy użyciu automatycznego układu. jakieś pomysły?

Czasami jest w porządku i nic się nie dzieje, innym razem całkowicie mnie psuje UIi nic się nie ładuje

znak
źródło
2
Z jakiegoś powodu usunięto doskonałą odpowiedź poniżej: github.com/nrbrook/NBUIKitMainThreadGuard
Fattie
Uratowało mi to co najmniej kilka godzin. Dzięki @Fattie
oyalhi
prawo @oyalhi. bądź ostrożny w użyciu, naprawdę mi się podobało, ale miałem też inne problemy - to trudne pole! mam nadzieję, że to pomoże!
Fattie
pół pokrewne pytanie
Miód

Odpowiedzi:

638

Musi zostać umieszczony w innym wątku, który umożliwia aktualizację interfejsu użytkownika, gdy tylko zakończy się wykonywanie funkcji wątku:

Modern Swift:

DispatchQueue.main.async {
    // Update UI
}

Starsze wersje Swift, przed Swift 3.

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

Cel C:

dispatch_async(dispatch_get_main_queue(), ^{
    // code here
});
znak
źródło
3
Aby to zadziałało w Celu C, umieść ^ (void) przed {w bloku kodu, a po nim średnikiem.
avance

4
Chociaż to nie boli, ^ (void) zamiast po prostu ^ nie jest konieczne. Wersja celu C odpowiedzi jest w porządku.
Keller

2
Działa dla mnie dobrze. Moim problemem jest zrobienie żądania sieciowego, a wewnątrz bloku ukończenia sukcesu zadzwoniłem do funkcji aktualizacji interfejsu użytkownika. Ponieważ UIKit nie jest bezpieczny dla wątków, należy ponownie wysłać wątek główny, aby zaktualizować interfejs użytkownika.
Rachel

1
Zobacz odpowiedź @Naishta na rozwiązanie Swift 3
Nathaniel

1
Dla Swift 3: DispatchQueue.main.async () { code }, jak powiedział
@Naishta
146

Otrzymujesz podobny komunikat o błędzie podczas debugowania za pomocą instrukcji drukowania bez użycia polecenia „dispatch_async”. Gdy pojawi się ten komunikat o błędzie, czas na użycie

Szybki 4

DispatchQueue.main.async { //code }

Szybki 3

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

Wcześniejsze wersje Swift

dispatch_async(dispatch_get_main_queue()){ //code }
Naishta
źródło
5
ponieważ składnia jest nieprawidłowa, powinna brzmieć: dispatch_async(dispatch_get_main_queue(), ^{ /* UI related code */ });Edytuj: Zaktualizowałem jego odpowiedź, formatowanie składni działa tam lepiej.
Zoltán,
1
lub, gdy jesteś w zamknięciu, zejdź do głównego wątku z wątku w tle, używając: self.performSelectorOnMainThread (Selector („yourFunction:”), withObject: 'yourArray / yourObject', waitUntilDone: true)
Naishta
1
Nie, nie, spójrz na składnię
Naishta
82

Błąd „ta aplikacja modyfikuje silnik autolayout z wątku w tle” jest rejestrowany w konsoli długo po wystąpieniu rzeczywistego problemu, więc debugowanie może być trudne bez użycia punktu przerwania.

Użyłem odpowiedzi @ markussvensson, aby wykryć mój problem i znalazłem go za pomocą tego Symbolicznego punktu przerwania (Debugowanie> Punkty przerwania > Utwórz symboliczny punkt przerwania ):

  1. Symbole: [UIView layoutIfNeeded]lub[UIView updateConstraintsIfNeeded]
  2. Stan: schorzenie: !(BOOL)[NSThread isMainThread]

wprowadź opis zdjęcia tutaj

Zbuduj i uruchom aplikację na emulatorze i powtórz kroki prowadzące do wyświetlenia komunikatu o błędzie (aplikacja będzie wolniejsza niż zwykle!). Xcode zatrzyma aplikację i zaznaczy wiersz kodu (np. Wywołanie func), który uzyskuje dostęp do interfejsu użytkownika z wątku w tle.

k06a
źródło
3
Hmm Głosowałem za tym, ponieważ wydawało się to mieć sens. Jednak wykonanie nie przerywa się i nadal pojawia się błąd w moich logach. Czy są inne warunki, których mogę użyć, aby ustawić symboliczny punkt przerwania?
Sjakelien
W moim przypadku się psuje. Jednak ślad stosu nie daje mi żadnej wskazówki, który widok jest odpowiedzialny. I nie wiem, jak interpretować pokazany kod asembleramovq 0x10880ba(%rip), %rsi ; "_wantsReapplicationOfAutoLayoutWithLayoutDirtyOnEntry:"
Reinhard Männer,
Czy nie spowoduje to, że aplikacja będzie działać wolno podczas debugowania?
Itachi,
@Itachi tak. Nie wiem, jak przyspieszyć.
k06a
2
Począwszy od Xcode 9 jest to wbudowana funkcja. Upewnij się tylko, że opcja „Główny sprawdzanie wątków” jest włączona w zakładce „Diagnostyka” w ustawieniach schematu
AndrewPo
24

Podczas próby aktualizacji wartości pola tekstowego lub dodania widoku podrzędnego w wątku w tle można uzyskać ten problem. Z tego powodu powinieneś umieścić ten rodzaj kodu w głównym wątku.

Aby uzyskać kolejkę główną, musisz owinąć metody wywołujące aktualizacje interfejsu użytkownika metodą dispatch_asynch. Na przykład:

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

ZMIENIONO - SWIFT 3:

Teraz możemy to zrobić zgodnie z następnym kodem:

// 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
źródło
23

Dla mnie ten komunikat o błędzie pochodzi z baneru z AdMob SDK.

Byłem w stanie śledzić początek do „WebThread” poprzez ustawienie warunkowego punktu przerwania.

warunkowy punkt przerwania, aby dowiedzieć się, kto aktualizuje interfejs użytkownika z wątku w tle

Następnie udało mi się pozbyć tego problemu, kapsułkując tworzenie banera za pomocą:

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

Nie wiem, dlaczego to pomogło, ponieważ nie widzę, jak ten kod został wywołany z wątku innego niż główny.

Mam nadzieję, że może pomóc każdemu.

markussvensson
źródło
1
Miałem ten sam problem. Byłem zdezorientowany, dlaczego wyjątek występował w wątku internetowym. Dokonałem tej samej zmiany co ty i teraz działa. Korzystam z nieco przestarzałej wersji admob sdk. Zastanawiam się, czy zostało to poprawione w najnowszej wersji. Dzięki za to. Nie sądzę, żebym to znalazł.
Larry,
1
Aktualizacja do najnowszej wersji AdMob rozwiązała dla mnie ten problem
JH95,
Dostaję przerwę w takim symbolicznym punkcie przerwania, ale nie pokazano kodu. :(
Victor Engel
20

Miałem ten problem od czasu aktualizacji do zestawu SDK dla systemu iOS 9, gdy dzwoniłem do bloku, który aktualizował interfejs użytkownika w module obsługi zakończenia żądania asynchronicznego NSURLConnection. Umieszczenie wywołania bloku w pliku dispatch_async przy użyciu narzędzia dispatch_main_queue rozwiązało problem.

Działa dobrze w iOS 8.

gąbczasty
źródło
10

Miałem ten sam problem, ponieważ używałem performSelectorInBackground.

Konstabl
źródło
Nie, musiałem robić rzeczy w tle. Wstawiłem wywołanie NSNotificationCenter do metody dispatch_async (dispatch_get_main_queue () i zadziałało
Bobby
Otrzymywałem dane z URLSessionDelegate, który następnie nazwał NSNotification, UIViewController odpowiedział na powiadomienie i tam użyłem DispatchQueue.main.async, aby pokazać użytkownikowi, czy dane były dobre, czy nie. Źle! - Rozwiązaniem jest umieszczenie powiadomienia w głównej kolejce. DispatchQueue.main.async {NotificationCenter.default.post (nazwa: NSNotification.Name (rawValue: networkNotificationNames.products.rawValue), obiekt: self, userInfo: [networkNotificationNames.products.rawValue: productList])}
iCyberPaul
7

Nie wolno zmieniać interfejsu użytkownika poza głównym wątkiem! UIKit nie jest bezpieczny dla wątków, więc jeśli to zrobisz, pojawią się powyższe problemy i inne dziwne problemy. Aplikacja może nawet ulec awarii.

Tak więc, aby wykonać operacje UIKit, musisz zdefiniować blok i pozwolić, aby był on wykonywany w głównej kolejce: np.

NSOperationQueue.mainQueue().addOperationWithBlock {

}
Chowdhury Md Rajib Sarwar
źródło
To nie jest dostępne w Xcode 7.2 i iOS 9.2. Jakaś inna alternatywa?
Jayprakash Dubey
7

Oczywiście robisz aktualizację interfejsu użytkownika w wątku z tyłu. Nie mogę dokładnie przewidzieć, gdzie jest Twój kod.

Oto niektóre sytuacje, które mogą się zdarzyć:

możesz robić coś w tle i nie używać. Będąc w tej samej funkcji, ten kod jest łatwiejszy do wykrycia.

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

wywołanie func wykonującego żądanie WWW wywołanie wątku w tle i jego moduł obsługi zakończenia wywołujący func wykonującego aktualizację interfejsu użytkownika. aby rozwiązać ten problem, sprawdź kod, w którym zaktualizowałeś interfejs użytkownika po wywołaniu żądania internetowego.

// 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
źródło
5

Główny problem z „Ta aplikacja modyfikuje silnik autolayout z wątku w tle” polega na tym, że wydaje się, że jest rejestrowany długo po wystąpieniu rzeczywistego problemu, co może bardzo utrudnić jego rozwiązanie.

Udało mi się rozwiązać problem, tworząc trzy symboliczne punkty przerwania.

Debugowanie> Punkty przerwania> Utwórz symboliczny punkt przerwania ...

Punkt przerwania 1:

  • Symbol: -[UIView setNeedsLayout]

  • Stan: schorzenie: !(BOOL)[NSThread isMainThread]

Punkt przerwania 2:

  • Symbol: -[UIView layoutIfNeeded]

  • Stan: schorzenie: !(BOOL)[NSThread isMainThread]

Punkt przerwania 3:

  • Symbol: -[UIView updateConstraintsIfNeeded]

  • Stan: schorzenie: !(BOOL)[NSThread isMainThread]

Dzięki tym punktom przerwania możesz łatwo uzyskać przerwę w wierszu, w którym niepoprawnie wywołujesz metody interfejsu użytkownika w wątku innym niż główny.

www.jensolsson.se
źródło
4

Miałem ten problem podczas ponownego ładowania danych w UITableView. Po prostu wysłanie przeładowania w następujący sposób naprawiło problem.

    dispatch_async(dispatch_get_main_queue(), { () -> Void in
        self.tableView.reloadData()
    })
ReshDev
źródło
To było dla mnie! Miałem wszystko inne w głównej kolejce, ale zabłąkany reloadData ()!
Oprimus
3

Miałem ten sam problem. Okazało się, że korzystałem z UIAlertstej potrzebnej głównej kolejki. Ale są przestarzałe .
Kiedy zmienił UIAlertssię do UIAlertController, już nie miał problemu i nie trzeba używać żadnego dispatch_asynckodu. Lekcja - zwróć uwagę na ostrzeżenia. Pomagają nawet wtedy, gdy się tego nie spodziewasz.

epaus
źródło
3

Masz już poprawną odpowiedź na kod z @Mark, ale tylko po to, by podzielić się moimi ustaleniami: Problem polega na tym, że prosisz o zmianę poglądu i zakładasz, że nastąpi to natychmiast. W rzeczywistości ładowanie widoku zależy od dostępnych zasobów. Jeśli wszystko ładuje się wystarczająco szybko i nie ma opóźnień, nic nie zauważysz. W scenariuszach, w których występuje opóźnienie z powodu zajętości wątku procesowego itp., Aplikacja działa w sytuacji, w której ma coś wyświetlić, mimo że nie jest jeszcze gotowa. Dlatego wskazane jest wysyłanie tych żądań w kolejkach asynchronicznych, aby były one wykonywane na podstawie obciążenia.

Mukund Agarwal
źródło
2

Miałem ten problem, kiedy korzystałem z TouchID, jeśli to pomaga komukolwiek innemu, zapakuj swoją logikę sukcesu, która prawdopodobnie robi coś z interfejsem użytkownika w głównej kolejce.

Stuart P.
źródło
2

Może to być coś tak prostego, jak ustawienie wartości pola tekstowego / etykiety lub dodanie widoku podrzędnego w wątku tła, co może spowodować zmianę układu pola. Upewnij się, że wszystko, co robisz z interfejsem, dzieje się tylko w głównym wątku.

Sprawdź ten link: https://forums.developer.apple.com/thread/7399

Kiran P. Nair
źródło
2

Miałem ten sam problem, gdy próbowałem zaktualizować komunikat o błędzie w UILabel w tym samym ViewController (aktualizacja danych podczas próby normalnego kodowania zajmuje trochę czasu). Użyłem DispatchQueuew Swift 3 Xcode 8 i działa.

FN90
źródło
2

Jeśli chcesz wyłapać ten błąd, użyj głównego pauzy sprawdzania wątków przy problemach. Naprawienie go jest przez większość czasu łatwe, rozwiązywanie problematycznej linii w głównej kolejce.

wprowadź opis zdjęcia tutaj

Rockdaswift
źródło
1
Co to dokładnie robi? „Główny moduł sprawdzania wątków” był domyślnie włączony i dodatkowo zaznaczyłem „Sanitizer wątków” i „Wstrzymaj problemy”, ale nadal wyświetla tylko komunikat „modyfikowanie z wątku w tle”, nie dodając do niego żadnych informacji.
Neph
Wstrzymuje wykonywanie aplikacji w punkcie, w którym interfejs użytkownika jest modyfikowany w wątku w tle.
rockdaswift
Dziwne, nie zrobiło tego w mojej aplikacji (Xcode 10.2.1). Musiałem ręcznie dodać punkty przerwania (jak opisano tutaj ), aby zatrzymać i skierować mnie do wiersza kodu.
Nef
Co mam zrobić, aby zobaczyć ten zestaw opcji?
David Rector,
Edytuj schemat celu
rockdaswift,
1

Dla mnie problem był następujący. Upewnij się, że performSegueWithIdentifier:jest wykonywany w głównym wątku:

dispatch_async (dispatch_get_main_queue(), ^{
  [self performSegueWithIdentifier:@"ViewController" sender:nil];
});
Offek
źródło
1

Swift 4,

Załóżmy, że jeśli wywołujesz jakąś metodę za pomocą kolejki operacji

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

Załóżmy, że wyszukiwanie funkcji Ulubione to:

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

jeśli zadzwonisz, cały kod w metodzie „searchFavourites” w głównym wątku, nadal będzie dawał błąd, jeśli aktualizujesz w nim jakiś interfejs użytkownika.

Ta aplikacja modyfikuje silnik autolayout z wątku tła po uzyskaniu dostępu do silnika z głównego wątku.

Więc użyj rozwiązania,

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

Dla tego rodzaju scenariusza.

Pramod More
źródło
1

Tutaj sprawdź tę linię z dzienników

$S12AppName18ViewControllerC11Func()ySS_S2StF + 4420

możesz sprawdzić, która funkcja wywołująca z wątku w tle lub gdzie wywołujesz metodę interfejsu API, musisz wywołać swoją funkcję z głównego wątku w ten sposób.

DispatchQueue.main.async { func()}

func () to funkcja, którą chcesz wywołać w wyniku powodzenia wywołania interfejsu API.

Dzienniki tutaj

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
źródło
0

Zetknąłem się również z tym problemem, widząc mnóstwo tych komunikatów i śladów stosu drukowanych na wydruku, gdy zmieniłem rozmiar okna na mniejszy niż jego początkowa wartość. Długo zastanawiając się nad tym problemem, pomyślałem, że podzielę się raczej prostym rozwiązaniem. Kiedyś włączyłem Can Draw Concurrentlyna NSTextViewIB. To mówi AppKit, że może wywołać draw(_:)metodę widoku z innego wątku. Po wyłączeniu nie otrzymuję już komunikatów o błędach. Nie miałem żadnych problemów przed aktualizacją do macOS 10.14 Beta, ale jednocześnie zacząłem modyfikować kod, aby działał z widokiem tekstowym.

Andreas
źródło