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



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

我很好奇为什么前 2 个 NSLog 消息显示一个对象而不是空(如最后一个 NSLog)。我使用的是最新的 Xcode 5.0.1 和 iOS 7 SDK。

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

这显示零:

  __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给出的承诺。它只承诺对象有效的最短时间。系统的其他部分可以随意附加平衡的保留/自动释放对,这将延迟释放,直到池耗尽。

用这一行:

self.data = [NSArray arrayWithArray:arr];

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

请记住,当删除对对象的所有强引用时,__weak 将清零。使用第二个数组,您仍然对第一个NSLog有很强的引用。

在第二个NSLog中,可能涉及访问阻止数组立即释放的属性的自动释放。编辑:有关详细信息,请参阅Rob Napier的回答。

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

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

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

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

如果要在循环中创建和释放大量对象(如示例中所示),请考虑在自定义发布池中执行此操作。

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

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

问题是您将数组值分配给某个变量,然后删除数组,但在 nslog 中打印分配数组的变量。所以肯定它不会打印空,它会打印对象

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

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

最新更新