60赫兹NSTimer和自动释放内存

时间:2011-07-13 22:38:02

标签: objective-c memory nstimer autorelease

我以60 fps的速度NSTimer开火。它更新了C ++模型,然后通过Quartz 2D绘制。这很有效,除非内存快速累积,即使我没有分配任何东西。仪器报告没有泄漏,但很多CFRunLoopTimers(我想从重复NSTimer?)似乎正在积累。单击窗口或按一个键清除它们中的大多数,这似乎指向自动释放池没有经常耗尽。我是否必须依靠事件来循环自动释放池,或者是否有更好的方法来清除内存?

感谢任何帮助,谢谢

萨姆

创建计时器(timer是一个ivar):

timer = [NSTimer scheduledTimerWithTimeInterval:1.0f / 60 target:self selector:@selector(update:) userInfo:nil repeats:YES];

update:方法:

- (void)update:(NSTimer *)timer {
    controller->Update();
    [self.view setNeedsDisplay:YES];
}

更新

在对此进行了一些讨论之后,我又做了一些额外的观察。

1。)[self.view setNeedsDisplay:YES] 似乎是产生这些CFRunLoopTimers的罪魁祸首。用[self.view display]替换它可以解决问题,但代价是性能。

2.。)将频率降低到20-30 fps并保持“[self.view setNeedsDisplay:YES]”也会导致问题消失。

这似乎意味着setNeedsDisplay:不喜欢被大量调用(可能会显示每秒更多的时间?)。我坦率地无法理解“过度调用”它的问题,如果所有这一切都告诉视图在eventloop结束时重新显示。

我相信我在这里遗漏了一些东西,非常感谢任何额外的帮助。

3 个答案:

答案 0 :(得分:6)

通常,正确的解决方案是围绕对象创建繁重的代码创建嵌套的NSAutoreleasePool。

但是在这种情况下,当计时器重新安排自己时,似乎对象是自动释放的 - 一段你无法控制的代码。并且你不能要求最顶层的自动释放池在不释放的情况下自行排出。

在您的情况下,解决方案是放弃NSTimer以进行帧速率同步,并改为使用CADisplayLink

CADisplayLink *frameLink = [CADisplayLink displayLinkWithTarget:self
                                                        selector:@selector(update:)];

// Notify the application at the refresh rate of the display (60 Hz)
frameLink.frameInterval = 1;

[frameLink addToRunLoop:[NSRunLoop mainRunLoop]
                forMode:NSDefaultRunLoopMode];

CADisplayLink用于将绘图与屏幕的刷新率同步 - 因此它似乎是您想要做的事情的一个很好的候选者。此外,当以60 Hz运行时,NSTimer的精度不足以与显示器刷新率同步。

答案 1 :(得分:1)

好吧,不管内存清理问题如何:

NSTimer的文档说:“由于典型的运行循环管理各种输入源,定时器的时间间隔的有效分辨率限制在50-100毫秒的数量级。”

1/60是大约的间隔。 16.6毫秒,所以你远远超出了NSTimer的有效分辨率。

您的后续注释表明将其频率降低到20-30 fps可将其固定... 20 fps将间隔时间设置为50 ms - 在记录的分辨率范围内。

文档还表明这不应该破坏任何东西......但是,我遇到了一些奇怪的情况,仪器引起了之前没有的内存问题。在没有安装Xcode或Instruments的情况下,您是否在Release版本中运行应用程序时出现内存问题/警告?

我想在这一点上我建议继续前进并尝试其他发布的答案中的工具。

答案 2 :(得分:0)

正如Kemenaran已经建议的那样,我也认为您应该尝试使用CADisplayLink对象。另一个原因是NSTimer的射击限制为50-100毫秒(source):

  

由于典型的运行循环管理各种输入源,因此定时器的时间间隔的有效分辨率限制在50-100毫秒的数量级。

另一方面,我不确定这会解决问题。根据您的描述,在我看来,事情可以像这样(或以类似的方式):

  1. 当您执行[self.view setNeedsDisplay:YES];时,框架会通过CFRunLoopTimer计划重绘视图;这解释了为什么有这么多人被创造出来;

  2. CFRunLoopTimer触发时,视图重新绘制,needsDisplay标记重置;

  3. 在你的情况下,当update的频率很高时,你会更频繁地调用setNeedsDisplay而不是实际发生刷新;因此,对于每次实际刷新,您都会多次调用setNeedsDisplay,并且还会创建多个CFRunLoopTimer;

  4. 在两个连续的实际刷新操作之间创建的所有CFRunLoopTimer,只有第一个被释放和销毁;其他人没有机会发射或找到needsDisplay标志已经重置的视图,因此可能会自行重新安排。

  5. 对于第4点:我认为最可能的解释是第一个:你建立一个CFRunLoopTimer s的队列,其频率远高于你“消耗”它的频率。我说重绘时间比update周期要长,因为你说当你调用[view display]时性能会受到影响。

    如果这是正确的,那么问题也会随着CADisplayLink而持续存在(因为它与调用更新的频率与重绘速度相比更高)并且唯一的解决方案是找到一种不同的方式来重绘(即,不使用setNeedsDisplay:YES

    事实上,我使用cocos2d源检查了setNeedsDisplay:YES几乎没有使用过。重绘(cocos2d提供60 fps的帧速率)是通过直接绘制到OpenGL缓冲区来完成的,我怀疑这是能够达到该帧速率的关键点。您还可以检查是否可以用CAEAGLLayer替换视图图层(这应该非常简单)并查看是否可以直接绘制到glBuffer

    我希望它有所帮助。请记住,我在这里做了许多假设,所以很可能是任何错误。我只是在提出我的推理。