在iOS5.1上运行较低帧率的CADisplayLink

我在我的iPhone应用程序中使用CADisplayLink

这是相关的代码:

 SMPTELink = [CADisplayLink displayLinkWithTarget:self selector:@selector(onTick)]; SMPTELink.frameInterval = 2;//30fps 60/n = fps [SMPTELink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; 

因此onTick被称为每帧30FPS (1/30秒)。 这在iOS6 +伟大的工程 – 完全正是我所需要的。 但是,当我在运行iOS5.1的iPhone 4上运行我的应用程序时,onTick方法的运行速度比iOS6稍慢。 几乎就像它运行它29FPS 。 一点点之后,它与iOS6 iPhone 5不同步。

onTick方法中的代码并不费时(这是我的想法之一),而不是iPhone,因为应用程序在运行iOS6的iPhone 4上运行良好。

iOS5.1 CADisplayLinkfunction是否CADisplayLink不同? 任何可能的解决方法/解决scheme?

我不能说与iOS 5.xv 6.x的差异,但是当我使用CADisplayLink ,我从来没有像每次迭代“移动x像素/点”这样的东西,而是我看timestamp (或者更准确地说,我的初始timestamp和当前timestamp之间的增量),并根据已经过了多less时间来计算位置,而不是通过了多less帧。 这样,帧速率不会影响运动的速度,而只会影响运动的速度。 (而且30和29之间的差别可能是难以区分的。)

引用CADisplayLink类参考 :

一旦显示链接与运行循环相关联,当屏幕的内容需要更新时,目标上的select器被调用。 目标可以读取显示链接的时间戳记属性来检索前一帧显示的时间。 例如,显示电影的应用程序可能会使用时间戳来计算下一个要显示的video帧。 执行自己的animation的应用程序可能会使用时间戳来确定显示的对象在即将到来的帧中的显示位置和方式。 持续时间属性提供了帧之间的时间量。 您可以在您的应用程序中使用此值来计算显示器的帧速率,下一帧将显示的大致时间,以及调整绘图行为,以便及时准备下一帧以显示。


作为一个随机的例子, 在这里我使用已经过去的秒数作为参数来animation一个UIBezierPath

或者,或者,如果您正在处理一系列UIImage帧,则可以如下计算帧号:

 @property (nonatomic) CFTimeInterval firstTimestamp; - (void)handleDisplayLink:(CADisplayLink *)displayLink { if (!self.firstTimestamp) self.firstTimestamp = displayLink.timestamp; CFTimeInterval elapsed = (displayLink.timestamp - self.firstTimestamp); NSInteger frameNumber = (NSInteger)(elapsed * kFramesPerSecond) % kMaxNumberOfFrames; // now do whatever you want with this frame number } 

或者,更好的是,为了避免丢失帧的风险,请继续以60 fps的速度运行,并确定帧是否需要更新,这样可以降低丢帧的风险。

 - (void)handleDisplayLink:(CADisplayLink *)displayLink { if (!self.firstTimestamp) self.firstTimestamp = displayLink.timestamp; CFTimeInterval elapsed = (displayLink.timestamp - self.firstTimestamp); NSInteger frameNumber = (NSInteger)(elapsed * kFramesPerSecond) % kMaxNumberOfFrames; if (frameNumber != self.lastFrame) { // do whatever you want with this frame number ... // now update the "lastFrame" number property self.lastFrame = frameNumber; } } 

但是经常,帧数根本不需要。 例如,要将一个UIView放在一个圆圈中,你可能会这样做:

 - (void)handleDisplayLink:(CADisplayLink *)displayLink { if (!self.firstTimestamp) self.firstTimestamp = displayLink.timestamp; CFTimeInterval elapsed = (displayLink.timestamp - self.firstTimestamp); self.animatedView.center = [self centerAtElapsed:elapsed]; } - (CGPoint)centerAtElapsed:(CFTimeInterval)elapsed { CGFloat radius = self.view.bounds.size.width / 2.0; return CGPointMake(radius + sin(elapsed) * radius, radius + cos(elapsed) * radius); } 

顺便说一句,如果你使用仪器来测量帧频,它看起来会比真正在设备上慢。 对于马特的评论,为了获得准确的帧速率,您应该在具有发布版本的实际设备上以编程方式进行测量。

Rob的回答是完全正确的。 你没有业务担心CADisplayLink的帧率; 事实上,你甚至不能期望计时器会像正常一样触发。 您的工作是根据所需的时间范围划分所需的animation,并通过累加时间戳来计算每次计时器触发时您实际获得的帧。

以下是我的书中的示例代码:

 if (self->_timestamp < 0.01) { // pick up and store first timestamp self->_timestamp = sender.timestamp; self->_frame = 0.0; } else { // calculate frame self->_frame = sender.timestamp - self->_timestamp; } sender.paused = YES; // defend against frame loss [_tran setValue:@(self->_frame) forKey:@"inputTime"]; CGImageRef moi3 = [self->_con createCGImage:_tran.outputImage fromRect:_moiextent]; self->_iv.image = [UIImage imageWithCGImage:moi3]; CGImageRelease(moi3); if (self->_frame > 1.0) { [sender invalidate]; self->_frame = 0.0; self->_timestamp = 0.0; } sender.paused = NO; 

在那个代码中, _frame值在0(我们刚刚开始animation)和1(我们已经完成了animation)之间运行,而在中间我只是做这个特定情况下需要绘制的帧。 为了使animation更长或更短,只需在设置_frame伊娃时乘以比例因子_frame

另外请注意,你不能在模拟器中testing,因为结果是毫无意义的。 只有设备正确运行CADisplayLink。

(例子来自这里: http : //www.apeth.com/iOSBook/ch17.html#_cifilter_transitions )