NSArray或NSMutableArray在使用ARC时可能会出现内存泄漏问题

4

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。



此外,这与内存泄漏无关。 - jrturton
5个回答

5
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:会添加一个额外的retain/autorelease,因为它们是平衡的,所以它可以这样做。你可以通过在不同的时间点释放自动释放池来演示这一点。
这显示了一个活动对象:
  __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

这里的教训是,您不应该假设一个对象将在自动释放池中的任何给定点处被释放。这并不是ARC给出的承诺。它只承诺对象将有效的最短时间。系统的其他部分可以随意附加平衡的保留/自动释放对,这将延迟到池排干时才进行释放。

你是说这个函数结束后,那两个对象(由weakObject和weakMutableObject指向)会被释放吗? - benck
weakObjectweakMutableObject 都指向同一个对象。根据当前的代码,当当前的自动释放池释放时,该对象将被释放。由于这是 viewDidLoad 方法,你应该预计在该方法结束后不久就会发生。无论如何,在下一个事件循环开始之前,它都会被释放。 - Rob Napier

2

使用这行代码:

self.data = [NSArray arrayWithArray:arr];

你最终得到了两个数组和两个对你的对象的强引用。然后你从第一个数组中删除了对象,但没有从第二个数组中删除。因此,这些对象仍然有一个强引用,仍然存活着。
请记住,只有当所有对对象的强引用都被移除时,__weak才会被清零。对于第二个数组,你仍然有一个对第一个NSLog的强引用。
对于第二个NSLog,可能涉及到访问属性的自动释放,这会防止数组被立即释放。请参见Rob Napier的答案以获取详细信息。
在第三个日志中,你正在设置:
self.tmpObject = [[BKObject alloc] initWithName:@""];

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


我认为这与self.data=[NSArray arraywitharray:arr]无关。将(weakMutableObject = arr[0];和weakObject = self.data[0];)更改为(self.tmpObject = arr[0]和self.tmpobject = self.data[0]),两种情况都会打印null。 - benck

0
这是对象工作的方式。您已经创建了一个对象,它被分配了一个内存位置,然后被放入一个本地的NSArray中,该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

0
问题在于你将数组值分配给某个变量,然后删除了数组,但在nslog中打印的是你分配数组的变量。因此它肯定不会打印null,而是打印对象。
  self.data = [NSArray arrayWithArray:arr];

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

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


变量是弱引用,不应该被计数到 ARC 中,我假设。 - benck
请点击此链接,您将了解有关ARC下弱引用局部变量的生命周期的信息:https://dev59.com/S2DVa4cB1Zd3GeqPbDdf - Hussain Shabbir
谢谢,现在我知道这是一个autoreleasepool问题。 - benck

0

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

如果您想在循环中创建和释放许多对象(如您的示例中),请考虑在自定义释放池内执行。

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


正如你所说,如果弱变量在函数外部,它就是空的。感谢提供自动释放池信息。 - benck

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接