此时不拥有的引用计数的减少不正确

时间:2012-05-12 23:18:54

标签: ios memory-management

我不明白这个,除非是因为我正在释放财产而不是ivar。有人可以解释这个问题吗?

    self.dataToBeLoaded = [[NSMutableData alloc] initWithLength:10000];
    [self.dataToBeLoaded release];

警告为Incorrect decrement of the reference count of an object that is not owned by the caller

dataToBeLoaded属性具有与其setter关联的retain属性。

我的理解是alloc init增加了保留计数,属性赋值增加了保留计数。由于我只有一次保留它,这就是我在分配后立即发布它的原因。

更新 - 一些实验结果:

由于我在下面的评论中注意到我收到了关于保留属性对合成二传手的影响的矛盾建议,我想我会使用上面的代码进行一些实验,并使用一些日志记录进行修改:

NSLog(@"retain 1 = %d", [dataToBeLoaded_ retainCount]);
self.dataToBeLoaded = [[NSMutableData alloc] initWithLength:10000];
NSLog(@"retain 2 = %d", [dataToBeLoaded_ retainCount]);
[self.dataToBeLoaded release];
NSLog(@"retain 3 = %d", [dataToBeLoaded_ retainCount]);

每个日志语句的结果为0,2和1.

显然,不可能进入alloc或init代码以查看保留计数从0到1到2.我可以将NSMutableData类子类化,但我的时间很短。

我知道很多人说你不能依赖retainCount属性的值,但我所拥有的似乎是一致的,我希望在代码的短范围内有合理的行为,就像示例中所示。因此,我倾向于认为先前的建议是正确的 - 保留属性是承诺在设置者中包含保留。所以这里我有来自alloc / init的保留和来自setter的调用的保留。因此,保留计数设置为2。

当我运行此代码时:

NSMutableData *theData;
NSLog(@"retain 1 = %d", [theData retainCount]);
theData= [[NSMutableData alloc] initWithLength:10000];
NSLog(@"retain 1a = %d", [theData retainCount]);
self.dataToBeLoaded = theData;
NSLog(@"retain 2 = %d", [theData retainCount]);
[self.dataToBeLoaded release];
NSLog(@"retain 3 = %d", [theData retainCount]);

每个日志语句的保留计数为0,1,2,1。

所以我有证据表明setter正在提供retain。这看起来更像是承诺而不是暗示,因为它实际上正在发生。

我愿意接受其他解释。我不想对此傲慢。我只想对正在发生的事情做好准备。我似乎警告(在这个问题的主题中)是非常虚假的,不用担心。

使用assign而不是retain作为@property语句中的属性,再完成一项实验。使用相同的代码:

NSMutableData *theData;
NSLog(@"retain 1 = %d", [theData retainCount]);
theData= [[NSMutableData alloc] initWithLength:10000];
NSLog(@"retain 1a = %d", [theData retainCount]);
self.dataToBeLoaded = theData;
NSLog(@"retain 2 = %d", [theData retainCount]);
[self.dataToBeLoaded release];
NSLog(@"retain 3 = %d", [theData retainCount]);

每个日志的保留计数为0,1,1(设置者未保留),然后是错误消息:message sent to deallocated instance。最后一个版本将保留计数设置为零,从而触发了重新分配。

更新2

最后更新 - 当使用您自己的代码覆盖合成的setter时,除非您的setter明确包含它,否则不再遵循retain属性。显然(这与我在其他主题中被告知的内容相矛盾)如果你想要的话,你必须在setter中包含你自己的保留。虽然我没有在这里测试它,但您可能需要先释放旧实例,否则它将被泄露。

此自定义setter不再具有@propety声明的属性属性:

- (void) setDataToBeLoaded:(NSMutableData *)dataToBeLoaded {
    dataToBeLoaded_ = dataToBeLoaded;
}

这是有道理的。覆盖合成的setter并覆盖所有声明的属性。使用合成的setter,在合成的实现中观察声明的属性。

@property属性表示如何实现合成setter的“承诺”。一旦你写了一个自定义setter,你就可以独立完成了。

4 个答案:

答案 0 :(得分:4)

关键是要考虑以下代码的作用。为了清楚起见,我会全文写出来:

[self setDataToBeLoaded:[[NSMutableData alloc] initWithLength:10000]];

这会创建一个具有+1保留计数的对象,并将其传递给setDataToBeLoaded:。 (*)然后抛弃它对该对象的引用,泄漏它。

[[self dataToBeLoaded] release];

调用dataToBeLoaded并释放返回的对象。没有任何承诺dataToBeLoaded返回的对象与传递给setDataToBeLoaded:的对象相同。您可能认为它们是相同的,并且查看您的代码,您可能会说服自己它将始终以这种方式工作,但这不是API承诺。

Antwan发布的代码是正确的:

NSMutableData *data = [[NSMutableData alloc] initWithLength:1000];
self.dataToBeLoaded = data;
[data release];

创建一个具有+1保留计数的对象。然后将其传递给方法,然后释放它。

或者,如果您愿意使用自动释放池,可以将其简化为:

self.dataToBeLoaded = [NSMutableData dataWithLength:1000];

(*)从技术上讲,这会将消息传递给self,这可能会也可能不会导致调用此方法,但这会使问题变得混乱。在大多数情况下,假装它是一个方法调用。但假装它只是设置属性。它真的会调用一些方法。


编辑:

也许这段代码会让问题更加清晰。它表明了常见的缓存解决方案:

.h
@interface MYObject : NSObject 
@property (nonatomic, readwrite, strong) NSString *stuff;
@end

.m
@interface MYObject ()
@property (nonatomic, readwrite, weak) MYStuffManager *manager;

@implementation MYObject

... Initialize manager ...

- (NSString*)stuff {
  return [self.manager stuffForObject:self];
}

- (void)setStuff:(NSString *)stuff {
   [self.manager setStuff:stuff forObject:self];
}

现在也许manager在背景中做了一些事情。也许它会缓存stuff的各种副本。也许它会复制它们。也许它将它们包装成其他物体。重要的是,您不能依赖-stuff始终返回传递给-setStuff:的同一对象。所以你当然不应该发布它。

请注意,标题中没有任何内容表示这一点,也没有任何内容。这不是来电者的事。但是如果调用者释放-stuff的结果,那么你将会遇到难以调试的崩溃。

@synthesize只是编写一些繁琐代码的简写(代码实现stuffsetStuff:读取和编写ivar)。但没有任何说明你必须使用@synthesize作为你的财产。

答案 1 :(得分:2)

我的猜测是方法

- (NSMutableData *)dataToBeLoaded;

不包含任何内存管理关键字,因此假设您拥有返回的数据,因此不应该释放它。

使用

NSMutableData *data = [[NSMutableData alloc] initWithLength:1000];
self.dataToBeLoaded = data;
[data release]; data = nil;

或者如果你真的需要它可以为什么不懒加载呢?

- (NSMutableData *)dataToBeLoaded;
{
    if (!_dataToBeLoaded) {
        _dataToBeLoaded = [[NSMutableData alloc] initWithLength:1000];
    }
    return _dataToBeLoaded;
}

答案 2 :(得分:1)

这只意味着您正在发布一个您不拥有的对象。

我会说直接用实例var调用它而不是使用getter,但不确定是否会修复分析警告。另外为什么不使用[NSMutableData dataWithLength:1000];这是自动释放的,因此消除了额外释放调用的需要(并且可能也会消除该警告!)

您可以通过其他方式解决问题:

NSMutableData *data = [[NSMutableData alloc] initWithLength:1000];
self.databToBeLoaded = data;
[data release];

答案 3 :(得分:0)

我提供了几个更新,我想回答这里发生了什么。通过一些测试结果,我的结论是这个警告是虚假的,这意味着它并没有真正识别不正确的代码。更新应该说明一切。它们在上面给出。