I am having issues with the following warning:
CoreAnimation: warning, deleted thread with uncommitted CATransaction; set CA_DEBUG_TRANSACTIONS=1 in environment to log backtraces.
I am using an NSOperation object to perform some calculations, once complete it sends a message back to the AppDelegate that then hides a progress bar and unhides some buttons. If I comment out the message back to the AppDelegate the warning goes away but the progress bar obviously remains visible and animated.
I am using xCode 4.4.1 and OSX 10.8.1, however, when I compile and run the code using the same version of xCode on OSX 10.7.4 I do not get the warning and the code runs as expected.
Setting the CA_DEBUG_TRANSACTIONS=1 environment variable shows the backtrace as coming from an NSControl setEnabled message in the AppDelegate.
The answer is probably staring me in the face but maybe I've had too much coffee!
In keeping with standard Cocoa paradigms, the recommended solution here is to perform your Core Animation work on the main thread, easily done with GCD:
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate redrawSomething];
});
In general it's poor form to call objects in contexts they don't expect, so a good rule of thumb is to always dispatch onto the main thread when delivering messages to external modules.
Some frameworks—like Core Location—with emit a log message if they are called from any context other than the main thread. Others will emit cryptic messages, such as your example here with Core Animation.
Your suspicions are right. If NSOperation completes before CoreAnimation is done performing, then you get a nice warning:
*CoreAnimation: warning, deleted thread with uncommitted CATransaction; set CA_DEBUG_TRANSACTIONS=1 in environment to log backtraces.*
This can also happen under some circumstances when a block that is dispatched on a queue triggers some work from CoreAnimation and returns before the CoreAnimation finishes.
The solution I use is simple: On a block or NSOperation that requests work from CoreAnimation, I check that the work has indeed been completed before exiting.
To give you a proof-of-concept example, this is a block to be dispatched on a dispatch queue. In order to avoid the warning, we check that the CoreAnimation is done before exiting.
^{
// 1. Creating a completion indicator
BOOL __block animationHasCompleted = NO;
// 2. Requesting core animation do do some work. Using animator for instance.
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context){
[[object animator] perform-a-nice-animation];
} completionHandler:^{
animationHasCompleted = YES;
}];
// 3. Doing other stuff…
…
// 4. Waiting for core animation to complete before exiting
while (animationHasCompleted == NO)
{
usleep(10000);
}
}
Another way of ensuring any UI drawing occurs on the main thread, as described by Numist, is using the method performSelectorOnMainThread:withObject:waitUntilDone:
or alternatively performSelectorOnMainThread:withObject:waitUntilDone:modes:
- (void) someMethod
{
[...]
// Perform all drawing/UI updates on the main thread.
[self performSelectorOnMainThread:@selector(myCustomDrawing:)
withObject:myCustomData
waitUntilDone:YES];
[...]
}
- (void) myCustomDrawing:(id)myCustomData
{
// Perform any drawing/UI updates here.
}
For a related post on the difference between dispatch_async()
and performSelectorOnMainThread:withObjects:waitUntilDone:
see Whats the difference between performSelectorOnMainThread and dispatch_async on main queue?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With