How to correctly stop and resume a CADisplayLink?

dontWatchMyProfile picture dontWatchMyProfile · Dec 7, 2011 · Viewed 11.3k times · Source

I figured out a big problem with CADisplayLink.

I have the most basic EAGLLayer with OpenGL ES 1.1 drawing a rotating triangle for testing. This needs a run loop method to be called at screen refresh rate, so I start the runloop like this:

- (void)startRunloop {
    if (!animating) {
        CADisplayLink *dl = [[UIScreen mainScreen] displayLinkWithTarget:self selector:@selector(drawFrame)];
        [dl setFrameInterval:1.0];
        [dl addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        self.displayLink = dl;

        animating = YES;
    }
}

- (void)stopRunloop {
    if (animating) {
        [self.displayLink invalidate];
        self.displayLink = nil;
        animating = NO;
    }
}

When the test app launches, I call -startRunloop. When I tap the screen, I call -stopRunloop. When I tap again, I call -startRunloop. And so forth. Ping Pong.

I'm measuring how often the -drawFrame method is called in 20 seconds, and NSLog it.

  • The FIRST start / stop cycle always performs at 100%. I get the maximum frame rate.

  • All SUBSEQUENT start / stop cycles only show about 80% of the performance. I get a significantly smaller frame rate. But: Always almost exactly the same, +/- 2 frames. With no tendency to degrade further even after 50 more start / stop cycles.

In conclusion: CREATING a CADisplayLink like I do above is fine, until it's invalidated or paused. After that, any new CADisplayLink does not perform well anymore. Even if it's created new and in the exact same way as before. Same is true if I pause / resume it by calling the -setPaused: method with YES / NO.

I've made sure with Allocations and VM Tracker Instruments that there is no memory management issue. I've also checked that the -invalidate method of CADisplayLink really gets called.

iOS 4.0 on the device (iPhone 4).

Why is that? Am I missing something?

Answer

Hector picture Hector · Sep 22, 2015

I think you what you really want is simply to pause and unpause your display link.

 - (void)createRunloop {
    CADisplayLink *dl = [[UIScreen mainScreen] displayLinkWithTarget:self selector:@selector(drawFrame)];
    [dl setFrameInterval:1.0];
    [dl addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    self.displayLink = dl;
    // start in running state, your choice.
    dl.paused = NO;
}
- (void)startRunloop {
    self.displayLink.paused = NO;
}
- (void)stopRunloop {
    self.displayLink.paused = YES;
}
- (void)destroyRunloop {
        [self.displayLink invalidate];
        self.displayLink = nil;
}