【问题标题】:60 hz NSTimer and autoreleased memory60 hz NSTimer 和自动释放内存
【发布时间】:2011-10-04 22:01:46
【问题描述】:

我有一个NSTimer 以 60 fps 的速度射击。它更新 C++ 模型,然后通过 Quartz 2D 进行绘制。这很好用,除了内存迅速积累,即使我没有分配任何东西。 Instruments 报告没有泄漏,但似乎有很多 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: 不喜欢被调用很多(也许每秒更多的时间然后可以显示?)。如果它所做的只是告诉在事件循环结束时重新显示视图,我坦率地无法理解“过度调用”它的问题。

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

【问题讨论】:

  • 你能贴出你创建定时器的代码和你的定时器事件的代码吗?

标签: objective-c memory nstimer autorelease


【解决方案1】:

通常正确的解决方案是围绕您的对象创建繁重的代码创建一个嵌套的 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 用于将绘图与屏幕的刷新率同步——所以它似乎是你想做的一个很好的选择。此外,NSTimer 在以 60 Hz 运行时不够精确,无法与显示刷新率同步。

【讨论】:

  • 感谢您的建议,我认为像 CADisplayLink(或 CVDisplayLink)这样的东西更适合我的工作。但是,我也很好奇为什么我的基本 NSTimer 方法会导致这个奇怪的内存问题。我已根据有关该问题的一些最新发现更新了我的问题。
【解决方案2】:

正如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 标志已重置的视图,因此可能会重新安排自己的时间。

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

如果这是正确的,那么 CADisplayLink 也会出现问题(因为与重绘速度相比,它与调用 update 的频率太高有关)并且唯一的解决方案是找到一种不同的方法来进行重绘(即,不使用setNeedsDisplay:YES)

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

我希望它有所帮助。请记住,我在这里提出了许多假设,因此很可能其中任何一个都是错误的。我只是提出我的推理。

【讨论】:

    【解决方案3】:

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

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

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

    您的后续说明表明,将其频率降低到 20-30 fps 可以修复它... 20 fps 使间隔达到 50 毫秒 - 在记录的分辨率范围内。

    文档还表明这不会破坏任何东西......但是,我遇到了一些奇怪的情况,即 Instruments 导致了以前不存在的内存问题。在没有附加 Xcode 或 Instruments 的情况下,您是否在 Release 版本中运行应用程序时遇到内存问题/警告?

    我想此时我建议您继续尝试其他已发布答案中的工具。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-24
      • 2013-07-13
      • 1970-01-01
      • 1970-01-01
      • 2011-01-11
      相关资源
      最近更新 更多