如何使用ARC在NSMutableArray中释放对象?

时间:2014-04-25 17:34:52

标签: ios objective-c macos memory-leaks automatic-ref-counting

我原来的项目漏了所以我搜索了泄漏。当我找到它时,我创建了一个简单的新项目。 该项目使用ARC,我添加的唯一代码如下。

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    int elements = 10000000;
    //memory usage 5,2 MB

    NSMutableArray *array = [NSMutableArray arrayWithCapacity:elements];
    //memory usage 81,7 MB

    for (int i = 0; i < elements; i++) {
        [array addObject:[NSObject new]];
    }
    //memory usage 234,3 MB

    [array removeAllObjects];
    //memory usage 234,3 MB

    array = nil;
    //memory usage 159,5 MB
}

在调用[array removeAllObjects]之后,应该释放数组中的所有NSObject,并且内存使用量应该再次为81,7 MB。 我做错了什么?

2 个答案:

答案 0 :(得分:2)

下面

NSMutableArray *array = [NSMutableArray arrayWithCapacity:elements];

您正在创建自动释放的对象(autorelease pool)。

  

许多程序会创建自动释放的临时对象。这些   对象添加到程序的内存占用,直到结束   块。在许多情况下,允许临时对象累积   直到当前事件循环迭代结束才导致   过度开销;但是,在某些情况下,您可以创建一个   大量临时对象大量增加内存   足迹和你想要更快地处置。在这些   在后一种情况下,您可以创建自己的自动释放池块。在   块的结尾,临时对象被释放,这通常是   导致他们的释放,从而减少程序的内存   足迹

使用@autoreleasepool {}方法[NSMutableArray arrayWithCapacity:elements]包裹

NSMutableArray *array;
@autoreleasepool {
    array = [NSMutableArray arrayWithCapacity:elements];
    // [NSMutableArray arrayWithCapacity:] creates object with retainCount == 1
    // and pushes it to autorelease pool

    // array = some_object; usually (and in this case also) is transformed by ARC to 
    // [array release]; [some_object retain]; array = some_object;

    // so here array will have retainCount == 2 and 1 reference in autorelease pool

} // here autorelease pool will call `release` for its objects.
// here array will have retainCount == 1

或将其更改为

NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:elements];

答案 1 :(得分:1)

您已经被可怕的自动释放池所困扰。基本上,为了使人们可以管理MRC(手动引用计数)而不是立即释放对象,可以将其交给自动释放池(NSAutoreleasePool的实例,它的文档提供了更多详细信息),这将保留对象,直到池稍后耗尽。可以设计ARC(自动参考计数),因此不需要自动释放机器,但保持与MRC的兼容性仍然存在。

池在运行循环周期结束时自动耗尽 - 即,当应用程序处理完事件时。但是,如果应用程序创建了大量临时对象,然后丢弃它们是程序的某些本地化部分,那么使用本地自动释放池可以大大减少最大内存使用量。并不是说这些临时物体不会被释放,只是它们的存活时间远远超过需要的时间。可以使用@autoreleasepool { ... }构造创建本地池。

您可以通过包裹整个applicationDidFinishLaunching:

的正文来查看示例中的效果
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
   @autoreleasepool
   {
      ...
   }
}

并逐步调试。

在您的真实代码中,您需要从产生大量临时对象的位置开始工作,以找到合适的点来添加自动释放池。

HTH。

<强>附录

当你认为它们不应该被释放时,你的数组中没有被释放的对象,你可以通过使用一个计算初始化和解除分配的简单类来测试它,例如:

@interface TestObject:NSObject

+ (void) showCounts;

@end

@implementation TestObject

static uint64_t initCount = 0, deallocCount = 0;

- (id) init
{
    self = [super init];
    if(self) initCount++;
    return self;
}

- (void) dealloc
{
    deallocCount++;
}

+ (void) showCounts
{
    NSLog(@"init: %llu | dealloc: %llu", initCount, deallocCount);
    initCount = deallocCount = 0;
}
@end

使用此代替NSObject并在完成测试后调用showCounts - 尝试使用/不使用自动释放等等。

你的记忆总是被释放,只是它的释放时间就是问题。有些对象最终出现在自动释放池中,默认值为每个事件清空一次,或者是本地池。

除非您针对单个事件创建了大量临时对象,否则您通常不会看到问题。考虑一下您是否在这里为您的应用程序追逐一个真正的问题。如果你是试图缓解问题的人之一是:

  • 避免使用<name>WithX...形式的便捷构造函数,它们是[[[C alloc] initWithX...] autorelease]的缩写。在许多(但不是全部)场合,编译器可以在方便构造函数返回之后从自动释放池中删除此类对象(并且您的情况似乎是可能失败的情况)。更好的方法是使用alloc / initnewalloc / init的简写)或(如果提供)newWithX...(速记) alloc / initWithX...)。在您的示例中尝试这些选项,并查看内存释放时间(而不是)的差异。

  • 位置很好的@autoreleasepool块。

HTH