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

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

4

5 回答 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:添加了一个额外的保留/自动释放,因为它们是平衡的,所以它是免费的。您可以通过在不同点排空自动释放池来非常简单地证明这一点。

这显示了一个活动对象:

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

于 2013-10-26T18:00:24.107 回答
2

有了这条线:

self.data = [NSArray arrayWithArray:arr];

您最终会得到两个数组和两个对对象的强引用。然后从第一个数组中删除对象,而不是从第二个数组中删除。因此,对象仍然有一个强引用并且仍然存在。

请记住,当对对象的所有强引用都已被删除__weak时,它会被清零。使用第二个数组,您仍然可以对第一个.NSLog

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

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

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

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

于 2013-10-26T17:53:41.207 回答
0

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

  self.data = [NSArray arrayWithArray:arr];

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

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

于 2013-10-26T18:07:17.840 回答
0

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

如果您想在循环中创建和释放大量对象,例如在您的示例中,请考虑在自定义释放池中执行此操作。

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

于 2013-10-26T17:51:18.367 回答
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
于 2013-10-26T18:01:21.477 回答