I'm experiencing a memory leak in my application and it seems to originate from CALayer. It looks as though it only affects old iPads; I see the problem with iPad 1 & 2, but iPad Air is ok. I have a crash report from an iPad 1 showing that my app was 'jettisoned' due to insufficient memory, this leak is my main suspect.
Background
During operation setNeedsDisplay is continuously called every 40ms by the network thread on various UIViews to update their visuals, see function below.
- (void)setNeedsRepaint
{
[self performSelectorOnMainThread:@selector(setNeedsDisplay) withObject:nil waitUntilDone:NO];
}
Simulating iPad2 and using the allocations instrument, I see that every time setNeedsDisplay is called the malloc 64 reference count permanently rises. The responsible library is libdispatch.dylib and the caller is dispatch_continuation_alloc_from_heap.
The iPad Air simulator doesn't show this issue, in this case the malloc 32 reference count only temporarily rises.
I am seeing the malloc 64 reference count rise even on occasions where setNeedsDisplay originates in the GUI thread and isn't dispatched via performSelectorOnMainThread.
Below is a screen shot of the allocations instrument. The malloc marked 3 is the leak in question. The mallocs marked 1 & 2 leak much slower, but are still a slight concern.
Steps taken
To rule out a memory leak in drawRect I commented out all the code between the braces, but the leaked memory still continues to accumulate.
If I don't override the drawRect method I don't see the leak, but I need to in order to draw and update my view. Nor do I see it if setNeedsDisplay is not called, I can call a dummy function instead via performSelectorOnMainThread with no memory leak.
I've tried using blocks and dispatch_async instead of performSelectorOnMainThread to run setNeedsDisplay on the GUI thread.
I also tried reducing the app down so that setNeedsDisplay is only repeatedly called on a single view. Then removing all pointers to that view so ARC cleans it up in the hope that the stray mallocs might get cleaned up with it.
I've tried setting the CALayer contents directly instead of calling setNeedsDisplay. It renders, but the malloc count rises in exactly the same way.
self.layer.contents = (__bridge id) _dummyCGImageRef;
After reading this I thought the leak maybe due to the queue becoming swamped. However, slowing the rate of my function call by 10 just made the memory leak grow 10x slower.
Conclusions
The leak really seems tied to CALayer rather than the dispatch queue and performSelectorOnMainThread. It looks like this issue is fixed in later iPads, but I still need a workaround for older models.
Questions
Has anyone got any tips for debugging this?
Is another instrument better suited to finding the exact cause?
Is this a peculiarity of the simulator and what I'm seeing isn't the reason my app is jettisoning?
Anyone know what's causing this? Is this an historic bug since it doesn't affect iPad Air?
I there a subclassing trick I could do with CALayer to prevent the backing store allocating memory, or another way I update my view visuals?
Thanks.
See Question&Answers more detail:
os