Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Objective C / iOS: Memory release with ARC (memory leak)

I am new to iOS/Objective-C and I do not understand the release of memory correctly. To test it, I created an empty ARC enabled iPhone-Project and created a very simple test class:

#import "MemTest.h"

@implementation MemTest {

}

-(void) start {
    for (int i = 0; i < 1500000; i++) {
        NSMutableString *myString = [NSMutableString string];

        // The appended string is 2000 characters long in the real test class.
        [myString appendString:@"12345678901234567890123456 <very long>"];

        if (i % 1000 == 0) {
            NSLog(@"i = %d", i);
        }

        myString = nil;
    }
}

@end

I simply start the test in the AppDelegate:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    MemTest *test = [[MemTest alloc] init];
    [test start];

    ....
}

The application (as expected) prints many nice numbers "i = xy" but the memory usage increases with every iteration and finally the application crashes:

....
2012-12-06 20:17:40.193 MemTestApp[19250:11303] i = 930000
2012-12-06 20:17:40.208 MemTestApp[19250:11303] i = 931000
MemTestApp(19250,0xac63f2c0) malloc: *** mmap(size=16777216) failed (error code=12)
*** error: can't allocate region
*** set a breakpoint in malloc_error_break to debug

So my question is: Why is the memory usage growing at all?

I thought by assigning nil, the memory should be released, when using ARC. What am I missing here?

like image 899
Leif Avatar asked Dec 06 '12 19:12

Leif


People also ask

What causes memory leaks in iOS?

A memory leak occurs when a given memory space cannot be recovered by the ARC (Automatic Reference Count) because it is unable to tell if this memory space is actually in use or not . One of the most common problems that generate memory leaks in iOS is retained cycles we will see it later.


4 Answers

A few things could be going wrong:

  1. You might not actually have ARC enabled. You should double-check that. Simplest way is to throw in a -retain in your code and make sure that throws a compiler error.

  2. ARC doesn't necessarily prevent objects from entering the autorelease pool. It tries to catch that if it can, but it doesn't make guarantees. Notably, at -O0 (no optimization), it frequently does not prevent objects from entering the autorelease pool. This is most likely what's going on for you.

  3. Even at higher optimization levels, ARC-enabled code is still not guaranteed to catch autoreleases.

If you stick an @autoreleasepool{} inside of your for loop you'll find the memory usage should go away. Alternatively, instead of using [NSMutableString string], you could try [NSMutableString new], which doesn't use the autorelease pool at all* but should otherwise behave identically in ARC code.

* well, NSMutableString is free to use the autorelease pool internally if it wants to

like image 188
Lily Ballard Avatar answered Sep 30 '22 15:09

Lily Ballard


So my question is: Why is the memory usage growing at all?

Because you're doing all that allocation inside a single loop. All those strings are autoreleased objects, and they'll be cleaned up as soon as the top autorelease pool is drained, which happens each time through the run loop. But you're not giving the run loop a chance to run at all, so the autorelease pool never gets drained and you run out of memory.

ARC frees you from worrying about managing individual objects, but you still need an understanding of how memory management in Objective-C works because ARC works according to the same rules.

like image 23
Caleb Avatar answered Sep 30 '22 14:09

Caleb


The [NSMutableString string] method returns an “autoreleased” object. That means the object is put into an “autorelease” pool. When the pool is drained, the object will be released (and, if there are no more strong references to it, it will be deallocated). The autorelease pool is automatically drained at the end of the run loop (just before the system goes to sleep waiting for another event).

When you write a loop that may allocate a large amount of autoreleased objects, you may want to manage your own autorelease pool:

-(void) start {
    for (int i = 0; i < 1500000; i++) {
        @autoreleasepool {
            NSMutableString *myString = [NSMutableString string];
            ...
        }
    }
}

That code creates a new autorelease pool at the start of each loop iteration, and drains it at the end of each loop iteration. So each string you create will be released at the end of the loop and, in your example code, be deallocated since nothing else retains it.

For more information, read the Advanced Memory Management Programming Guide, particularly the “Using Autorelease Pool Blocks” chapter.

like image 25
rob mayoff Avatar answered Sep 30 '22 16:09

rob mayoff


I don't think that autoreleased objects get released in a different manner just because your project has ARC. Actually the autorelease pool works in the same exact way: since you are allocating a lot of objects inside the loop the pool is never drained while iterating.

You should try by forcing an new autoreleasepool directly inside the loop body to see if this fixes your problem. Of course this could be overkill, you could try by splitting the loop in two nested loops to have just an autorelease pool from time to time, eg

for (int i = 0; i < TOTAL_STEPS; ++i) {
  @autoreleasepool {
    for (int j = 0; j < STEP_SIZE; ++j) {
      ..
    }
  }
}

I don't even think that setting a local variable to nil could make the difference in your situation, since it's a variable local to the scope of the loop body, the compiler already knows you couldn't use it anywhere else.

like image 23
Jack Avatar answered Sep 30 '22 14:09

Jack