KugelnMMXVI KugelnMMXVI - 1 month ago 16x
Objective-C Question

Memory not fully freed

I just started creating an app using SceneKit and SpriteKit and ARC for the first time. I noticed that the memory usage is quickly increasing when I switch between different Views. My first thought was that I have memory leaks but I am not sure now. The behavior even occurs in this basic example:

for(int r=0;r<9999999;r+=1){
NSString *s=[NSString stringWithFormat:@"test%i",r];

From my understanding an NSString Object is created and directly released in this loop. I've tried this example in the iPhone-Simulator and on an iPhone and it makes the app use several hundreds MB of RAM after this loop is executed. (I am checking the memory usage with the Xcode debug navigator)

I am obviously misunderstanding something. Why is this example still retaining memory afterwards?


You could also create a new project: iOS -> Game -> Game Technology: SceneKit

Then add this into viewDidLoad:

for(int r=0;r<999999;r+=1){
SCNNode *tn=[SCNNode node];

The memory will peak at 550MB and go down to 300MB which would be to much if there objects were fully released and removed from the RAM.

Rob Rob
  1. Don't rely on NSString for memory diagnostics. It has fairly atypical behavior.

    This is a not-uncommon scenario, one that I've seen on S.O. more than once, that in an attempt to reduce some complicated memory problem to something simpler, the developer creates a simplified example using NSString, unaware that choosing that particular class introduces curious, unrelated behaviors. The new "Debug Memory Graph" tool or the old tried-and-true Instruments (discussed below) is the best way to diagnose the underlying issues in one's code.

  2. As an aside, you talk about releasing objects immediately. If your method doesn't start with alloc, new, copy or mutableCopy, the object returned will not deallocated immediately after falling out of scope, because they're autorelease objects. They're not released until the autorelease pool is drained (e.g., you yield back to the run loop).

    So, if your app's "high water" mark is too high, but memory eventually falls back to acceptable levels, then consider the choice of autorelease objects (and/or the introducing of your own autorelease pools). But generally this autorelease vs non-autorelease object distinction is somewhat academic unless you have a very long running loop in which you're allocating many objects prior to yielding back to the run loop.

    In short, autorelease objects don't affect whether objects are deallocated or not, but merely when they are deallocated. I only mention this in response to the large big for loop and the contention that objects should be deallocated immediately. The precise timing of the deallocation is impacted by the presence of autorelease objects.

  3. Regarding your rapid memory increase in your app, it's likely to be completely unrelated to your example here. The way to diagnose this is to use Instruments (as described in WWDC 2013 Fixing Memory Issues). In short, choose "Product" - "Profile" and choose the "Leaks" tool (which will grab the essential "Allocations" tool, as well), exercise the app, and then look at precisely what was allocated and not released.

    Also, Xcode 8's "Debug Object Graph" tool is incredibly useful, too, and is even easier to use. It is described in WWDC 2016's Visual Debugging with Xcode. With this tool you can see a list of objects in the left panel, and when you choose one, you can see the object graph associated with that object, so you can diagnose what unresolved references you might still have:

    enter image description here

  4. By the way, you might try simulating a memory warning. Cocoa objects do all sorts of caching, some of which is purged when there's memory pressure.

  5. If you turned on any memory debugging options (e.g., zombies) on your scheme, be aware that those cause additional memory growth as it captures the associated debugging information. You might want to turn off any debugging options before analyzing leaked, abandoned or cached memory.

Bottom line, if you're seeing growth of a couple of kb per iteration and none of the objects that you instantiate are showing up and you don't have any debugging options turned on, then you might not need to worry about it. Many Cocoa objects are doing sundry cacheing that is outside of our control and it's usually negligible. But if memory is growing by mb or gb every iteration (and don't worry about the first iteration, but only subsequent ones), then that's something you really need to look at carefully.