Why doesn't ARC free memory when using [NSDate

2019-04-24 08:53发布

问题:

I've written some Objective-C application that seems to work fine, except for an ever-growing memory footprint. I'm using ARC under the last version of Xcode 4.6.2. My system is 10.7.5.

I'm very new to Objective-C and need some help figuring out what's going on with my memory. I've narrowed down the problem to explaining why the following basic piece of code behaves like it does.

This code is added in the vanilla Cocoa-based application template that Xcode gives (ARC enabled).

Case A

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification{
    NSDate* d;
    for(int i=0; i<1000000; i++){
        d = [[NSDate alloc] init];
    }
}

Everything goes as expected, ARC reclaims the memory on the fly. Namely, the memory usage history is very flat. When the for loop ends, the app takes about 25MB memory.

Case B

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification{
    NSDate* d;
    for(int i=0; i<1000000; i++){
        d = [NSDate date];
    }
}

Here things are really mysterious to me. When just running the app, the (real) memory usage keeps increasing to about 53MB and then stays there, forever.

However, when running the allocations profiler tool, I can see that at the end of the for loop, all the objects are deallocated, very much like you would expect with an autorelease pool. Also, enclosing the body of the for loop with an @autoreleasepool{} makes case B behave like case A (as expected).

So, three questions:

  1. Under ARC, what is the difference between using the "autoreleased" [NSDate date] and the alloc init object? (I thought there would be close to none from other questions here.)
  2. Why doesn't ARC seem to kick in when running the code?
  3. Why is there a difference in the memory behavior of the profiled application and the actual application?

回答1:

Autorelease objects are released eventually, whereas in the alloc/init case they are released as soon as they're not used anymore.

This behavior causes your objects to persist in memory for the whole loop in case they are autoreleased for the being released later on, whereas if you alloc/init them, a release method is sent within the loop body.

You can easily make the body loop memory-efficient by wrapping it in a @autoreleasepool like follows:

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification{
    @autoreleasepool {
        NSDate* d;
        for(int i=0; i<1000000; i++){
            d = [NSDate date];
        }
    }
}

This will give a hint to ARC, signaling that you want to create and release an autorelease pool at every loop iteration.

In the specific case the easiest option would probably be to use alloc/init methods, in order for the compiler to do the right thing automatically, but if you have a loop body with many factory methods that return autoreleased instances, the @autoreleasepool block is probably a great way to go.

As a final remark, @autoreleasepool is not an ARC exclusive. It used to exist since LLVM 3.0 it, and with sufficient modern targets (namely iOS5 and OSX 10.7 on) it's known to be much faster than the old-fashioned NSAutoreleasePool.



回答2:

[NSDate date] is creating an auto-released object that will be released the next time your program enters the event loop.

The other case is released within the loop by ARC.

If you really want to do something like this you can create your own auto-release pool and drain it periodically. See for example Objective-C: Why is autorelease (@autoreleasepool) still needed with ARC?