10

我想澄清一些事情。

可以说我有以下代码:

- (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 在这种情况下是否做同样的事情,或者我的对象被释放是否有另一种解释?

谢谢!

4

3 回答 3

20

安德鲁回答了您的主要问题,是的,您的自动释放池将在主运行循环的每个循环中耗尽。viewDidLoad因此,当您返回到主运行循环时,其中创建的任何自动释放对象都可能会立即耗尽。它们肯定不会保留“直到应用程序终止”。

但我们应该小心:您显然假设这些对象被添加到自动释放池中。对这个假设的一些警告:

  1. 在过去(并且仍然需要 ARC-MRC 互操作性),当从名称不以allocnewcopy或开头的方法返回对象时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,因为它已经过高度优化,并不总是符合标准的内存管理实践。

于 2013-11-07T17:04:37.820 回答
7

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

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

于 2013-11-07T16:24:23.193 回答
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

过渡到 ARC 发行说明中的​​ Apple说明

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

从clang Clang 3.4文档OBJECTIVE-C AUTOMATIC REFERENCE COUNTING (ARC)中,听起来release在为其分配新值时会调用对象

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

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

于 2013-11-08T02:54:36.753 回答