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.
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.
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()
}