Calling setNeedsDisplay from GLKView drawRect with/without dispatch_async

946 Views Asked by At

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:

  1. What is happening in that one second between the "Jo" logs?
  2. Why is dispatch_async making a difference?
  3. 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?

1

There are 1 best solutions below

0
On BEST ANSWER

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:

  1. Nothing special really. It is just waiting.
  2. It makes the difference as it triggers the refresh pipeline.
  3. Not a bad practice at all but be careful how you do this procedure if later more elements will be calling this.

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 and releaseAnimation. These 2 would increase or decrease an integer value retainAnimationCount then override its setter so that:

  1. If the count is increases from zero the display link starts
  2. If the count is decreases to zero the display link is stopped

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.