NSArray或NSMutableArray使用ARC可能发生内存泄漏

时间:2013-10-26 17:36:15

标签: ios objective-c memory-leaks nsmutablearray nsarray

BKObject是一个自定义对象,我想将多个BKObject放入数组中。

BKViewController:

#import <UIKit/UIKit.h>
#import "BKObject.h"

@interface BKViewController : UIViewController

@property (strong, nonatomic) NSArray *data;
@property (weak, nonatomic) BKObject *tmpObject;

@end

BKViewController.m:

#import "BKViewController.h"

@implementation BKViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    NSMutableArray *arr = [[NSMutableArray alloc] init];
    for(NSInteger i = 0; i < 100000; i++){
        [arr addObject:[[BKObject alloc] initWithName:@""]];
    }

    self.data = [NSArray arrayWithArray:arr];

    __weak BKObject *weakMutableObject = arr[0];
    [arr removeAllObjects];
    NSLog(@"%@", weakMutableObject); // print out the object, why?

    __weak BKObject *weakObject = self.data[0];
    self.data = nil;
    NSLog(@"%@", weakObject); // print out the object again, but why?


    self.tmpObject = [[BKObject alloc] initWithName:@""];
    NSLog(@"%@", self.tmpObject); // print null, very clear

}


@end

我很好奇为什么前两个NSLog消息显示一个对象而不是null(如在上一个NSLog中)。 我正在使用最新的Xcode 5.0.1和iOS 7 SDK。

5 个答案:

答案 0 :(得分:4)

NSMutableArray *arr = [[NSMutableArray alloc] init];
for(NSInteger i = 0; i < 100000; i++){
    [arr addObject:[[BKObject alloc] initWithName:@""]];
}

好的,所以在这一点上,我们有一堆由数组保留的对象。

self.data = [NSArray arrayWithArray:arr];

现在,在这一点上,我们有两堆不同数组保留的对象。

__weak BKObject *weakMutableObject = arr[0];
[arr removeAllObjects];
NSLog(@"%@", weakMutableObject); // print out the object, why?

因为arr[0]指向的对象也由self.data保留。

__weak BKObject *weakObject = self.data[0];
self.data = nil;
NSLog(@"%@", weakObject); // print out the object again, but why?

这个有点有趣。 “问题”是arrayWithArray:正在添加额外的保留/自动释放,因为它们是平衡的,所以可以自由地执行。你可以通过在不同的点消耗自动释放池来证明这一点。

这显示了一个实时对象:

  __weak NSObject *weakObject;
  self.data = [NSArray arrayWithArray:arr]; // Note outside nested autorelease pool
  @autoreleasepool {
    ...    
    weakObject= self.data[0];
    self.data = nil;
  }
  NSLog(@"%@", weakObject); // print out the object

这显示nil:

  __weak NSObject *weakObject;
  @autoreleasepool {
    self.data = [NSArray arrayWithArray:arr]; // Note inside nested autorelease pool
    ...   
    weakObject= self.data[0];
    self.data = nil;
  }
  NSLog(@"%@", weakObject); // print nil

这里的教训是,您不应该假设对象将在autorelease块中的任何给定点解除分配。这不是ARC给出的承诺。它只承诺对象有效的最小时间。系统的其他部分可以随意添加平衡的保留/自动释放对,这将延迟释放,直到池耗尽。

答案 1 :(得分:2)

这一行:

self.data = [NSArray arrayWithArray:arr];

最终会有两个数组和两个对象的强引用。然后从第一个数组中删除对象,但不从第二个数组中删除。因此,对象仍然有一个强大的参考,并仍然活着。

请记住,当对该对象的所有强引用都被删除时,__weak将被清零。对于第二个数组,您仍然可以对第一个NSLog进行强引用。

使用第二个NSLog,可能存在一个自动释放涉及访问属性,阻止阵列立即释放。 编辑:有关详细信息,请参阅Rob Napier的答案。

使用第三个日志,您正在设置:

self.tmpObject = [[BKObject alloc] initWithName:@""];

self.tmpObject是弱参考。由于您只对该对象有弱引用,因此该属性会立即归零。

答案 2 :(得分:0)

这可能是因为你仍然在同一个自动发布池中。哪个范围适用于您的功能。尝试在函数范围之外设置弱引用(例如作为属性)并调用函数在另一个函数内创建和释放,然后您应该看到对象的释放。

如果你想在循环中创建和释放很多对象,比如在你的例子中考虑在自定义发布池中这样做。

看看: https://developer.apple.com/library/mac/documentation/cocoa/conceptual/memorymgmt/articles/mmAutoreleasePools.html

答案 3 :(得分:0)

这是对象的工作方式。你已经创建了一个被分配了一个内存位置的对象,然后它被放入一个本地的NSArray中,然后跟踪它,然后在最终放入实例变量(self.data)之前将它放入另一个本地数组中。因此,此时您的对象在技术上具有3作为保留计数,因此在您的代码中,您已经释放了两次,这是它在NSLog语句中打印的原因。

尝试使用以下代码:

NSString *a = @"1";

NSMutableArray *arr = [[NSMutableArray alloc] init];
for (int i = 0; i < 10000; i++) {
    [arr addObject:a]; // Retain count 1
}

self.myArr = arr; // Retain count 2
NSString *test = arr[0];
[arr removeAllObjects];
NSLog(@"%@", test); // Prints ... Good! Retain count is 1

NSString *test1 = self.myArr[0];
self.myArr = nil;
NSLog(@"%@", test1); // Crash as object gone

答案 4 :(得分:0)

问题是您是将数组值分配给某个变量然后删除了数组,但是在nslog中,您正在打印已分配数组的变量。如此defintely它将不会打印null它将打印对象

  self.data = [NSArray arrayWithArray:arr];

__weak BKObject *weakMutableObject = arr[0];
[arr removeAllObjects];
NSLog(@"%@", weakMutableObject); // print     
out the object, why?

像这样的便利构造函数的返回值必须是自动释放的对象*。这意味着当前的autoreleasepool已保留该对象,并且在池耗尽之前不会释放它。因此,您几乎可以保证此对象至少在您的方法持续时间内存在 - 尽管您可能不应该依赖此行为。

相关问题