How to free objects in NSMutableArray using ARC?

My original project was leaking, so I was looking for a leak. When I found it, I created a simple new project. The project uses ARC, and I added only the following code.

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { int elements = 10000000; //memory usage 5,2 MB NSMutableArray *array = [NSMutableArray arrayWithCapacity:elements]; //memory usage 81,7 MB for (int i = 0; i < elements; i++) { [array addObject:[NSObject new]]; } //memory usage 234,3 MB [array removeAllObjects]; //memory usage 234,3 MB array = nil; //memory usage 159,5 MB } 

After calling [array removeAllObjects], all NSObjects in the array should be freed, and memory usage should be another 81.7 MB. What am I doing wrong?

+6
source share
2 answers

You were bitten by a frightening pool of abstracts. In fact, so that MRC (manual reference counting) is controlled by people instead of immediately releasing the object, it can be transferred to the autofill pool ( NSAutoreleasePool instance, it contains additional information) that will save the object until the pool is later drained . ARC (automatic reference counting) can be designed so that automatic processing equipment is not necessary, but remains compatible with MRC.

The pool automatically merges at the end of the run cycle, i.e. when the application has finished processing the event. However, if the application creates many temporary objects and then discards them, this is some localized part of the program, then using a local pool of auto resources can significantly reduce the maximum memory usage. The point is not that such temporary objects will not be released, they simply will live much longer than necessary. A local pool can be created using the @autoreleasepool { ... } construct.

You can see the effect in your example by wrapping the whole body of applicationDidFinishLaunching:

 - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { @autoreleasepool { ... } } 

and going through the debugger.

In your real code, you need to return from the point that creates many temporary objects in order to find a suitable point for adding a pool of autoresists.

NTN.

Adding

These are not objects in your array that are not freed when you think they need it, you can check this using a simple class that considers initialization and freeing, for example:

@interface TestObject: NSObject

 + (void) showCounts; @end @implementation TestObject static uint64_t initCount = 0, deallocCount = 0; - (id) init { self = [super init]; if(self) initCount++; return self; } - (void) dealloc { deallocCount++; } + (void) showCounts { NSLog(@"init: %llu | dealloc: %llu", initCount, deallocCount); initCount = deallocCount = 0; } @end 

Use this instead of NSObject and call showCounts after you showCounts done with your test - try with / without auto-repeat, etc.

Your memory is always freed, it’s just the time it is released, and this is the problem. Some objects fall into the autorun pool, either by default, which is omitted once per event, or local.

If you do not create many temporary objects in response to a single event, you will usually not see a problem. Think about whether you are pursuing a real problem for your application here. If you are among the things to try to alleviate the problem, follow these steps.

  • Avoid using convenience constructors like <name>WithX... , which are abbreviated values ​​for [[[C alloc] initWithX...] autorelease] . In many cases, but not in all cases, the compiler can delete such objects from the autocomplete pool right after the convenience constructor returns (and your case looks like one in which there may be a failure). It is better to use alloc / init , new (short for alloc / init ) or, if provided, newWithX... (short for alloc / initWithX... ). Try these options in your example and see the differences in when (and not if) memory is freed.

  • Well placed @autoreleasepool blocks.

NTN

+1
source

Here

 NSMutableArray *array = [NSMutableArray arrayWithCapacity:elements]; 

you create an autorelease pool .

Many programs create temporary objects that are auto-implemented. These objects add to the end of the program memory block. In many situations, allowing temporary objects to accumulate until the end of the current iteration of the event loop does not lead to excessive overhead; however, in some situations, you can create a large number of temporary objects that are substantially added to memory and you want to get rid of faster. In these, in the latter case, you can create your own autoresist pool block. At the end of the block, temporary objects are freed, which usually leads to their release, thereby reducing program memory footprint

Wrap with the @autoreleasepool {} [NSMutableArray arrayWithCapacity:elements] method:

 NSMutableArray *array; @autoreleasepool { array = [NSMutableArray arrayWithCapacity:elements]; // [NSMutableArray arrayWithCapacity:] creates object with retainCount == 1 // and pushes it to autorelease pool // array = some_object; usually (and in this case also) is transformed by ARC to // [array release]; [some_object retain]; array = some_object; // so here array will have retainCount == 2 and 1 reference in autorelease pool } // here autorelease pool will call `release` for its objects. // here array will have retainCount == 1 

or change it to

 NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:elements]; 
+2
source

All Articles