web-dev-qa-db-fra.com

iOS9 Cette application modifie le moteur d'autolayout à partir d'un thread en arrière-plan, ce qui peut entraîner une corruption du moteur et des accidents étranges

Je viens de télécharger le dernier XCode (7.1 beta) et j'ai commencé à jouer avec iOS9.

J'avais une application fonctionnant parfaitement sans aucune erreur dans iOS8, mais maintenant, l'erreur suivante est générée par le remplacement de la méthode drawRect dans une classe UITableViewCell:

"Cette application modifie le moteur d'autolayout à partir d'un thread en arrière-plan, ce qui peut entraîner une corruption du moteur et des blocages bizarres. Cela provoquera une exception dans une version ultérieure."

voici la trace: 

Stack:(
0   CoreFoundation                      0x000000010a749f65 __exceptionPreprocess + 165
1   libobjc.A.dylib                     0x0000000109dcfdeb objc_exception_throw + 48
2   CoreFoundation                      0x000000010a749e9d +[NSException raise:format:] + 205
3   Foundation                          0x0000000109b442e5 _AssertAutolayoutOnMainThreadOnly + 79
4   Foundation                          0x00000001099a4ece -[NSISEngine withBehaviors:performModifications:] + 31
5   UIKit                               0x000000010b9d425b -[UIView(AdditionalLayoutSupport) _withAutomaticEngineOptimizationDisabledIfEngineExists:] + 58
6   UIKit                               0x000000010b9d4d9e -[UIView(AdditionalLayoutSupport) updateConstraintsIfNeeded] + 254
7   UIKit                               0x000000010b702760 -[UITableViewCellContentView updateConstraintsIfNeeded] + 185
8   UIKit                               0x000000010b9d5ab3 -[UIView(AdditionalLayoutSupport) _updateConstraintsAtEngineLevelIfNeeded] + 272
9   UIKit                               0x000000010b1e6274 -[UIView(Hierarchy) _updateConstraintsAsNecessaryAndApplyLayoutFromEngine] + 159
10  UIKit                               0x000000010b1f5d84 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 710
11  QuartzCore                          0x000000010ae1059a -[CALayer layoutSublayers] + 146
12  QuartzCore                          0x000000010ae04e70 _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 366
13  QuartzCore                          0x000000010ae04cee _ZN2CA5Layer28layout_and_display_if_neededEPNS_11TransactionE + 24
14  QuartzCore                          0x000000010adf9475 _ZN2CA7Context18commit_transactionEPNS_11TransactionE + 277
15  QuartzCore                          0x000000010ae26c0a _ZN2CA11Transaction6commitEv + 486
16  QuartzCore                          0x000000010ae2737c _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv + 92
17  CoreFoundation                      0x000000010a675967 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
18  CoreFoundation                      0x000000010a6758d7 __CFRunLoopDoObservers + 391
19  CoreFoundation                      0x000000010a66ae4c CFRunLoopRunSpecific + 524
20  CoreFoundation                      0x000000010a71e011 CFRunLoopRun + 97
21  SDWebImage                          0x000000010971773c -[SDWebImageDownloaderOperation start] + 1868
22  Foundation                          0x0000000109961e47 __NSOQSchedule_f + 194
23  libdispatch.dylib                   0x000000010d93849b _dispatch_client_callout + 8
24  libdispatch.dylib                   0x000000010d91e8ec _dispatch_queue_drain + 2215
25  libdispatch.dylib                   0x000000010d91de0d _dispatch_queue_invoke + 601
26  libdispatch.dylib                   0x000000010d920a56 _dispatch_root_queue_drain + 1420
27  libdispatch.dylib                   0x000000010d9204c5 _dispatch_worker_thread3 + 111
28  libsystem_pthread.dylib             0x000000010dc80a9d _pthread_wqthread + 729
29  libsystem_pthread.dylib             0x000000010dc7e3dd start_wqthread + 13
)

Voici la méthode drawRect:

override func drawRect(rect: CGRect) {

    super.drawRect(rect)

    let cellRect = rect

    // Values
    let buttonBoxX = CELL_MARGIN + CELL_MARGIN/2
    let buttonBoxY = cellRect.height - buttonBoxHeight
    let buttonBoxWidth = rect.width - CELL_MARGIN * 3


    // Set Button Box
    let buttonBoxRect = CGRectMake(buttonBoxX, buttonBoxY, buttonBoxWidth, buttonBoxHeight )
    let buttonBox = UIBezierPath(roundedRect: buttonBoxRect, byRoundingCorners: [.BottomRight, .BottomLeft], cornerRadii: CGSize(width: CORNER_RADIUS, height: CORNER_RADIUS)) // Create the path
    UIColor.whiteColor().setFill() // Set the Fill to be white

    buttonBox.fill()


}

D'après ce que je comprends (cf. cette question ), des calculs complexes, etc., doivent être effectués sur un thread d'arrière-plan et la mise à jour de l'interface utilisateur sur le thread principal, car l'interface utilisateur n'est pas thread-safe.

Cependant, si j'utilise ce qui suit: 

override func drawRect(rect: CGRect) {

    super.drawRect(rect)

    let cellRect = rect

    // Values
    let buttonBoxX = CELL_MARGIN + CELL_MARGIN/2
    let buttonBoxY = cellRect.height - buttonBoxHeight
    let buttonBoxWidth = rect.width - CELL_MARGIN * 3


    // Set Button Box
    let buttonBoxRect = CGRectMake(buttonBoxX, buttonBoxY, buttonBoxWidth, buttonBoxHeight )
    let buttonBox = UIBezierPath(roundedRect: buttonBoxRect, byRoundingCorners: [.BottomRight, .BottomLeft], cornerRadii: CGSize(width: CORNER_RADIUS, height: CORNER_RADIUS)) // Create the path
    UIColor.whiteColor().setFill() // Set the Fill to be white

    dispatch_async(dispatch_get_main_queue(), {
        buttonBox.fill()
    })


}

Je reçois maintenant une erreur CGContext…

<Error>: CGContextSaveGState: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable.
<Error>: CGContextSetFlatness: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable.
<Error>: CGContextAddPath: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable.
<Error>: CGContextDrawPath: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable.
<Error>: CGContextRestoreGState: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable.
<Error>: CGContextSaveGState: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable.
<Error>: CGContextSetFlatness: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable.
<Error>: CGContextAddPath: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable.
<Error>: CGContextDrawPath: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable.
<Error>: CGContextRestoreGState: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable.

Aucune suggestion ?

SOLUTION: OK. voici le deal. Je chargeais une image dans un UIImageView de manière asynchrone, mais je ne la "peignais" pas dans l'interface utilisateur sur le thread principal mais sur un thread d'arrière-plan. En outre; J'appelais setNeedsDisplay sur UITableViewCell après avoir ajouté l'image à l'interface utilisateur, appelant ainsi la méthode drawRect à nouveau… mais cette fois sur le fil d'arrière-plan. 

12
Benjamin

La première erreur que vous constatez ne semble pas liée à votre code drawRect:. Votre drawRect: a l'air bien. Il ne fait rien de particulièrement compliqué. Si votre chemin était plus complexe, vous voudriez probablement le mettre en cache, mais il est probablement correct tel quel. Plus probablement, vous essayez de modifier l'interface utilisateur ailleurs sur un thread d'arrière-plan. Cela peut uniquement apparaître lorsque vous remplacez drawRect: car cela modifie le mode de mise à jour de l'interface utilisateur (sans un drawRect: personnalisé, le système peut éventuellement appliquer des transformations simples). Vous devez rechercher l'endroit où vous passez un appel UIKit sur un fil d'arrière-plan.

En cas de doute, s'il commence par UI, vous ne pourrez probablement pas l'utiliser sur un thread d'arrière-plan. Ce n'est pas tout à fait vrai (vous pouvez créer une UIBezierPath sur un thread d'arrière-plan et même le dessiner dans un contexte autre que l'écran), mais en première approximation, c'est une bonne chose à rechercher.

Ce code est tout simplement incorrect:

dispatch_async(dispatch_get_main_queue(), {
    buttonBox.fill()
})

L'appel à drawRect: devrait déjà avoir lieu be dans la file d'attente principale (la distribution dans cette file d'attente n'est donc pas utile). Si ce n'est pas le cas, voir ci-dessus. Au moment où ce bloc s'exécute, le cycle de dessin est terminé, il n'y a donc plus de contexte dans lequel dessiner.

22
Rob Napier

En gros, This application is modifying the autolayout engine from a background thread, which can lead to engine corruption and weird crashes. This will cause an exception in a future release indique que vous ne devez pas mettre à jour l'interface utilisateur, en particulier la partie liée à l'autolayout dans les threads non-UI. Pour réparer, utilisez dispatch_async(dispatch_get_main_queue()) { // your code }, ou

Swift 3:


DispatchQueue.main.async {
    buttonBox.fill()
}
2
superarts.org

Devrait essayer Symbolic Breakpoint pour détecter le problème: -  enter image description here

Ensuite, mettez votre code de mise à jour de l'interface utilisateur dans le fil principal

DispatchQueue.main.async {}
0
Abdul Hameed