How to correctly stop and resume a CADisplayLink?

2020-07-03 04:22发布

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?

5条回答
该账号已被封号
2楼-- · 2020-07-03 04:52

I am using

[displayLink removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

see if it works for you

查看更多
贼婆χ
3楼-- · 2020-07-03 04:57

Try this in Swift,

displayLink?.remove(from: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode)
查看更多
姐就是有狂的资本
4楼-- · 2020-07-03 05:03

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;
}
查看更多
等我变得足够好
5楼-- · 2020-07-03 05:06

For Swift 4.2

displayLink.remove(from: RunLoop.current, forMode: RunLoop.Mode.common)
查看更多
男人必须洒脱
6楼-- · 2020-07-03 05:12

You create a new display link instance with every start call. Have you tried just reusing the same instance?

Also, you could always just pause the display link.

查看更多
登录 后发表回答