Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Autorelease pools and when release is called under iOS

I wanted to get something clarified.

Lets say I have the following code:

- (void) viewDidAppear:(BOOL)animated {
  [super viewDidAppear:animated];
  for (int i = 0; i < 5000000; i++) {
    NSString *s = [NSString stringWithFormat:@"Hello, %@!", @"World"];
  }
}

This will create 5 million autoreleased strings within this function call. I was expecting this to keep those objects around until the termination of the application, as the only @autoreleasepool I see is the one wrapping the application instantiation in main.m. That's not the case, though. At the end of this function call, it seems they all get their release called and are removed from memory.

This document:

https://developer.apple.com/library/mac/documentation/cocoa/reference/foundation/Classes/NSAutoreleasePool_Class/Reference/Reference.html

States that "The Application Kit creates an autorelease pool on the main thread at the beginning of every cycle of the event loop, and drains it at the end, thereby releasing any autoreleased objects generated while processing an event."

That makes sense to me, but this is under UIKit, and not Application Kit. My question is, does UIKit/Cocoa Touch do the same thing in this case, or is there another explanation for my objects getting released?

Thanks!

like image 426
James Richard Avatar asked Nov 07 '13 16:11

James Richard


People also ask

What is an autorelease pool release message?

At the end of the autorelease pool block, objects that received an autorelease message within the block are sent a release message—an object receives a release message for each time it was sent an autorelease message within the block. // . . . // . . .

How do I use autorelease pools if I use arc?

If you use Automatic Reference Counting (ARC), you cannot use autorelease pools directly. Instead, you use @autoreleasepool blocks. For example, in place of: @autoreleasepool { // Code benefitting from a local autorelease pool. }

What is an autorelease pool block?

Jump To… Autorelease pool blocks provide a mechanism whereby you can relinquish ownership of an object, but avoid the possibility of it being deallocated immediately (such as when you return an object from a method).

What happens when autorelease is sent to an object?

Any object (such as fileContents) sent an autorelease message inside the autorelease pool block is released at the end of the block. After an autorelease pool block, you should regard any object that was autoreleased within the block as “disposed of.” Do not send a message to that object or return it to the invoker of your method.


1 Answers

Andrew answered your main question that, yes, your autorelease pool will be drained on every cycle of the main run loop. So any autorelease objects created in viewDidLoad may promptly get drained when you yield back to the main run loop. They certainly won't be retained "until the termination of the application."

But we should be careful: You're clearly assuming that these objects are being added to an autorelease pool. A few caveats to this assumption:

  1. In the past (and still required for ARC-MRC inter-operability), when returning objects from methods whose names did not start with alloc, new, copy, or mutableCopy, those objects would autorelease objects, deallocated only when the autorelease pool was drained (i.e. when you yielded back to the run loop).

  2. But ARC has gotten smarter about minimizing the need for autorelease pools (see http://rentzsch.tumblr.com/post/75082194868/arcs-fast-autorelease, which discusses callerAcceptsFastAutorelease, now called callerAcceptsOptimizedReturn invoked by prepareOptimizedReturn), so you often will not see this autorelease behavior. So, if both the library and the caller are using ARC, objects may not be placed in the autorelease pool, but rather ARC will cleverly release them immediately if they're not needed.

    With contemporary ARC projects, autorelease pools are generally not needed. But certain special cases, one can still benefit from using autorelease pools. I'll outline one of those cases below.

Consider the following code:

#import "ViewController.h"
#import <sys/kdebug_signpost.h>

typedef enum : NSUInteger {
    InnerLoop = 1,
    MainLoop = 2
} MySignPostCodes;

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"test" withExtension:@"png"];
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        kdebug_signpost_start(MainLoop, 0, 0, 0, 1);
        for (int j = 0; j < 500; i++) {
            NSData *data = [NSData dataWithContentsOfURL:fileURL];
            UIImage *image = [[UIImage alloc] initWithData:data];
            NSLog(@"%p", NSStringFromCGSize(image.size));  // so it's not optimized out
            [NSThread sleepForTimeInterval:0.01];
        }
        kdebug_signpost_end(MainLoop, 0, 0, 0, 1);
    });
}

@end

The following code will add 500,000 objects to the autorelease pool, which will only be drained when I yield back to the run loop:

no pool

In this case, you might use an autorelease pool to minimize the high water mark:

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"test" withExtension:@"png"];
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        kdebug_signpost_start(MainLoop, 0, 0, 0, 1);
        for (int j = 0; j < 5; j++) {
            @autoreleasepool {
                kdebug_signpost_start(InnerLoop, 0, 0, 0, 2);
                for (long i = 0; i < 100; i++) {
                    NSData *data = [NSData dataWithContentsOfURL:fileURL];
                    UIImage *image = [[UIImage alloc] initWithData:data];
                    NSLog(@"%p", NSStringFromCGSize(image.size));  // so it's not optimized out
                    [NSThread sleepForTimeInterval:0.01];
                }
                kdebug_signpost_end(InnerLoop, 0, 0, 0, 2);
            }
        }
        kdebug_signpost_end(MainLoop, 0, 0, 0, 1);
    });
}

@end

pool

Bottom line, with ARC, it's not always obvious when it used an autorelease object and when it explicitly releases it when the variable falls out of scope. You can always confirm this by examining the behavior in Instruments.

As an aside, I'd be wary about drawing too many general memory management conclusions when using the NSString class, as it has been highly optimized and doesn't always conform to standard memory management practices.

like image 118
Rob Avatar answered Oct 26 '22 18:10

Rob