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

时间:2013-11-07 16:02:37

标签: ios objective-c cocoa-touch nsautoreleasepool

我想澄清一些事情。

假设我有以下代码:

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

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

本文件:

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

表示“Application Kit在事件循环的每个循环开始时在主线程上创建一个自动释放池,并在最后将其排出,从而释放处理事件时生成的任何自动释放的对象。”

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

谢谢!

3 个答案:

答案 0 :(得分:20)

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

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

  1. 过去(并且仍然需要ARC-MRC互操作性),当从名称不以allocnewcopy开头的方法返回对象时或者mutableCopy,这些对象会自动释放对象,仅在自动释放池耗尽时释放(即当您返回到运行循环时)。

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

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

  3. 请考虑以下代码:

    #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
    

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

    no pool

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

    @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

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

    顺便说一句,在使用NSString类时,我会因为得出太多一般的内存管理结论而保持警惕,因为它已经过高度优化,并不总是符合标准的内存管理实践。

答案 1 :(得分:7)

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

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

答案 2 :(得分:0)

我认为当你将新对象分配给用来存放对象的引用时,原始对象会立即释放(如果没有其他任何东西指向它 - 引用计数变为零)使用ARC并假设默认strong引用,如循环示例中所示。

MyObject *object = [[MyObject alloc] init]; // obj1, ref count 1 because strong
object = [[MyObject alloc] init]; // obj2, ref count of obj1 should be 0
                                  // so obj1 gets released

Apple在Transitioning to ARC Release Notes

中说明
  

尝试不再考虑保留/释放调用的位置,而是考虑应用程序算法。考虑对象中的“强弱”指针,对象所有权以及可能的保留周期。

听起来release在为对象分配新值时被调用, 来自clang Clang 3.4 documentation OBJECTIVE-C AUTOMATIC REFERENCE COUNTING (ARC)

  

评估赋值运算符时会发生赋值。该   语义因资格而异:

     

对于__strong对象,首先保留新的指针;第二,   lvalue加载了原始语义;第三,新的指针是   用原始语义存储到左值中;最后,老了   指针被释放。这不是原子地执行的;外部   必须使用同步才能使这个安全   并发加载和存储。