【问题标题】:Autorelease pools and when release is called under iOS自动释放池以及何时在 iOS 下调用释放
【发布时间】:2013-11-19 09:04:15
【问题描述】:

我想澄清一些事情。

假设我有以下代码:

- (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

声明“应用程序工具包在事件循环的每个周期开始时在主线程上创建一个自动释放池,并在结束时将其排出,从而释放在处理事件时生成的所有自动释放对象。”

这对我来说很有意义,但这是在 UIKit 下,而不是在 Application Kit 下。我的问题是,UIKit/Cocoa Touch 在这种情况下是否会做同样的事情,或者对于我的对象被释放是否有其他解释?

谢谢!

【问题讨论】:

    标签: ios objective-c cocoa-touch nsautoreleasepool


    【解决方案1】:

    Andrew 回答了您的主要问题,是的,您的自动释放池将在主运行循环的每个循环中耗尽。因此,当您返回主运行循环时,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 项目,通常不需要自动释放池。但是在某些特殊情况下,仍然可以从使用自动释放池中受益。我将在下面概述其中一种情况。

    考虑以下代码:

    #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 个对象添加到自动释放池中,只有当我返回到运行循环时才会耗尽:

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

    @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
    

    最重要的是,对于 ARC,当它使用自动释放对象以及在变量超出范围时显式释放它时并不总是很明显。您始终可以通过检查 Instruments 中的行为来确认这一点。

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

    【讨论】:

    • "下面的代码会添加 500,000 个对象到自动释放池中,只有当我返回到运行循环时才会被耗尽" ARC 可以像第二个代码一样立即释放它,也许开启优化。
    • 我选择了 [NSString stringWithFormat:] 因为我知道返回的对象调用了 autorelease。所有这些我以前都知道,除了每个事件周围的游泳池,但谢谢你的澄清。希望它会帮助其他偶然发现这个问题的人:)
    • 如果object = nil 从内存中删除原始对象,那么object = [MyObject myObject] 可能从内存中删除原始对象。为什么会存放在 ARC 下的 autorelease pool 中?
    • @stefanB 如果您有一个自动释放对象([MyObject myObject] 代码创建),object = nil(或只是让对象超出范围)不会“删除”对象。它只是将指针归零并将所述对象留在自动释放池中,并且只有在池耗尽时才会释放该对象。
    • 起初,我想知道[NSData dataWithContentsOfURL:fileURL] 是否使用 MRC 编译虽然这似乎是一个疯狂的想法,但即使我使用 [[NSData alloc] initWithContentsOfURL:fileURL]; 代替或将 NSData 放在 for 循环之外,我仍然得到同样的结果。没错,在[[UIImage alloc] initWithData:data] 方法中,有些东西是自动释放的,很想知道它是什么以及为什么苹果会这样做。 :(
    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

      我假设当您将新对象分配给用于保存对象的引用时,原始对象立即被释放(如果没有其他指向它 - 引用计数变为零)使用 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
      

      Transitioning to ARC Release Notes中的苹果笔记

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

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

      在评估赋值运算符时会发生赋值。这 语义因限定而异:

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

      【讨论】:

        猜你喜欢
        • 2013-04-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-08-11
        • 2013-04-16
        相关资源
        最近更新 更多