I am building a custom GLKView
using on-demand rendering. Most of the time the view will only redraw on touch events (and this is working), but sometimes there are short animations where I would like to redraw in a loop.
My first attempt looked like this:
-(void)drawRect:(CGRect)rect {
NSLog(@"Jo");
glClearColor(1, 0, 0, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
[self setNeedsDisplay];
}
My understanding coming from Android
was, that this should keep clearing the screen as fast as possible and logging lots of "Jo".
What actually happens is that "Jo" is logged roughly once per second, the view is never cleared at all, yet CPU
use remains 0.
If I change
[self setNeedsDisplay];
to
dispatch_async(dispatch_get_main_queue(), ^{
[self setNeedsDisplay];
});
everything works exactly as expected.
As far as I understand drawRect
is called from the main thread anyway, so why does the dispatch_async
make a difference?
So now I have three questions:
- What is happening in that one second between the "Jo" logs?
- Why is
dispatch_async
making a difference? - Is it bad practice to use this approach in production?
Thank you very much!
EDIT:
There is one more thing I don't understand.
When I'm using the [self setNeedsDisplay];
method, all other calls on the main queue seem to be starving. Touch events no longer fire and a callback from RestKit never gets delivered. Does [self setNeedsDisplay];
somehow not get added at the end of the queue, but in the beginning?
In that one second nothing is happening most likely as nothing has triggered the redraw. This pipeline is quite complicated but methods such as
setNeedsDisplay
will do a bit more work when called on the main thread as it will notify the window hierarchy that it has changes and will redraw elements that are needed to redraw. This pipeline is most likely connected to the main run loop which will only be accessed from the main thread.So when you call it from some other thread you do actually mark the view that it needs to redraw but you do not notify the run loop to actually trigger the procedure to redraw.
So:
For the good/bad practice depends on the situation but what I would do is rather create a class containing the display link. I would add 2 methods such as
retainAnimation
andreleaseAnimation
. These 2 would increase or decrease an integer valueretainAnimationCount
then override its setter so that:The display link would then either call a delegate, a given block or simply hardcoded call to
setNeedsDisplay
for the given view. The class itself can revert the output call to the main thread but release and retain calls can be called from any thread.EDIT:
How this class is the used in your case you will call retain method once your animation begins and release method once it has finished animation. All the rest should already be handled in the class itself. The benefits are mostly if you have multiple "animation" objects there will be no extra calls to the refresh method, no problems with interleaving, when to start or when to stop, what thread you are calling it from... But do make sure you do not mark the retain count property as
nonatomic
as you should still keep it thread safe.