自动释放池以及在iOS下调用发布时

Jam*_*ard 10 cocoa-touch objective-c nsautoreleasepool ios

我想澄清一些事情.

可以说我有以下代码:

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

这将在此函数调用中创建500万个自动释放的字符串.我希望这可以保留这些对象直到应用程序终止,因为我看到的唯一的@autoreleasepool是在main.m中包装应用程序实例化的那个.但事实并非如此.在这个函数调用结束时,它们似乎都被调用了它们并从内存中删除.

这个文件:

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

"应用程序工具包在事件循环的每个循环开始时在主线程上创建自动释放池,并在最后将其排出,从而释放处理事件时生成的任何自动释放的对象."

这对我来说很有意义,但这是在UIKit下,而不是Application Kit.我的问题是,UIKit/Cocoa Touch在这种情况下做了同样的事情,还是有另一种解释我的对象被释放?

谢谢!

Rob*_*Rob 20

安德鲁回答了你的主要问题,是的,你的自动释放池将在主运行循环的每个循环中耗尽.因此,viewDidLoad当您回到主运行循环时,创建的任何自动释放对象可能会立即耗尽.它们肯定不会被保留"直到申请终止".

但我们应该小心:您明确假设这些对象被添加到自动释放池中.这个假设有几点需要注意:

  1. 在过去(现在仍然需要ARC-MRC互操作性),从名字没下手方法返回的对象时alloc,new,copy,或者mutableCopy,这些对象会自动释放对象,当自动释放池被抽干只释放(即当您回到了运行循环中.

  2. 但是ARC最大限度地减少了对自动释放池的需求(请参阅http://rentzsch.tumblr.com/post/75082194868/arcs-fast-autorelease,其中讨论了callerAcceptsFastAutorelease,现在callerAcceptsOptimizedReturn调用了prepareOptimizedReturn),所以你经常不会看到这个autorelease行为.因此,如果库和调用者都使用ARC,则对象可能不会放在自动释放池中,而是如果不需要ARC,ARC会立即巧妙地释放它们.

    对于现代ARC项目,通常不需要自动释放池.但是在某些特殊情况下,人们仍然可以从使用自动释放池中受益.我将在下面概述其中一个案例.

请考虑以下代码:

#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
Run Code Online (Sandbox Code Playgroud)

以下代码将向自动释放池添加500,000个对象,只有在我回到运行循环时才会耗尽:

没有游泳池

在这种情况下,您可以使用自动释放池来最小化高水位线:

@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
Run Code Online (Sandbox Code Playgroud)

池

最后,使用ARC,当它使用自动释放对象时,并且当变量超出范围时显式释放它时,并不总是很明显.您可以通过检查仪器中的行为来确认这一点.

另外,在使用NSString该类时,我会对在绘制太多通用内存管理结论时保持警惕,因为它已经过高度优化,并不总是符合标准内存管理实践.


And*_*sen 7

是的,UIKit做同样的事情.系统创建的主线程自动释放池在每个运行循环周期结束时耗尽.最好不要在自己的代码中依赖这个确切的生命周期.如果您手动创建新线程(使用例如NSThread),则您负责在该线程上创建自动释放池.

编辑:Rob的回答提供了一些关于ARC下行为的更好的附加信息.一般来说,可以说,由于ARC能够进行一些优化,对象不太可能最终进入自动释放池.