I have an NSTimer
firing at 60 fps. It updates a C++ model and then draws via Quartz 2D. This works well except memory accumulates quickly even though I am not allocating anything. Instruments reports no leaks but many CFRunLoopTimers
(I guess from the repeating NSTimer
?) seem to be accumulating. Clicking the window or pressing a key purges most of them which would seem to point to an autorelease pool not being drained frequently enough. Do I have to rely on events to cycle the autorelease pool(s) or is there a better way to clear out the memory?
Any help is appreciated, Thanks
-Sam
Timer creation (timer
is an ivar):
timer = [NSTimer scheduledTimerWithTimeInterval:1.0f / 60 target:self selector:@selector(update:) userInfo:nil repeats:YES];
update:
method:
- (void)update:(NSTimer *)timer {
controller->Update();
[self.view setNeedsDisplay:YES];
}
Update:
After messing around with this a little more I've made a couple of additional observations.
1.) [self.view setNeedsDisplay:YES]
seems to be the culprit in spawning these CFRunLoopTimers
. Replacing it with [self.view display]
gets rid of the issue but at the cost of performance.
2.) Lowering the frequency to 20-30 fps and keeping `[self.view setNeedsDisplay:YES]' also causes the issue to go away.
This would seem to imply setNeedsDisplay:
doesn't like to be called a lot (maybe more time's per second then can be displayed?). I frankly can't understand what the problem with "overcalling" it if all it does is tell the view to be redisplayed at the end of the eventloop.
I am sure I am missing something here and any additional help is greatly appreciated.
Usually the right solution would be to create a nested NSAutoreleasePool around your object-creations-heavy code.
But in this case, it seems the objects are autoreleased when the timer re-schedule itself — a piece of code you can't control. And you can't ask the topmost autorelease pool to drain itself without releasing it.
In your case, the solution would be to drop your NSTimer for frame-rate syncing, and to use a CADisplayLink
instead:
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 is made to synchronize the drawing to the refresh rate of the screen — so it seems like a good candidate for what you want to do. Besides, NSTimer are not precise enough to sync with the display refresh rate when running at 60 Hz.
Well, regardless of the memory-cleanup issue:
The documentation for NSTimer says: "Because of the various input sources a typical run loop manages, the effective resolution of the time interval for a timer is limited to on the order of 50-100 milliseconds."
1/60 is an interval of approx. 16.6 milliseconds so you're well beyond the effective resolution of NSTimer.
Your followup note indicates that lowering its frequency to 20-30 fps fixes it... 20 fps brings the interval to 50 ms -- within the documented resolution.
Documentation also indicates that this shouldn't break anything... however, I've encountered some odd situations were Instruments caused memory issues that weren't previously there. Do you get memory issues/warnings running the app in Release build, without Xcode or Instruments attached?
I guess at this point I'd recommend just moving on and trying out the tools in the other posted answers.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With