【问题标题】:Incorrect decrement of reference count not owned at this point此时不拥有的引用计数的不正确递减
【发布时间】:2012-05-21 00:59:43
【问题描述】:

我不明白这个,除非是因为我释放的是财产而不是 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 属性具有与其设置器关联的保留属性。

我的理解是 alloc init 增加了保留计数,而属性分配增加了保留计数。由于我只有一个保留它一次,所以我在分配后立即释放它。

更新——一些实验结果:

由于我在下面的 cmets 中注意到我收到了关于保留属性对合成设置器的作用的相互矛盾的建议,我想我会使用上面的代码做一个小实验,并通过一些日志记录进行修改:

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 属性的值,但我所拥有的似乎是一致的,我希望在示例中显示的代码的短范围内有合理的行为。所以我倾向于相信之前的建议是正确的——retain 属性是一个在 setter 中包含一个 retain 的承诺。所以在这里我有来自 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。

所以我有证据表明二传手提供了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]);

每条日志的retain count是0、1、1(setter没有retain),则报错:message sent to deallocated instance。上一个版本将保留计数设置为零,这触发了释放。

更新 2

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

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

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

这是有道理的。覆盖一个综合的设置器,你会覆盖所有声明的属性。使用合成的 setter,在合成的实现中观察到声明的属性。

@property 属性表示关于如何实现合成设置器的“承诺”。编写自定义设置器后,您就可以自己动手了。

【问题讨论】:

    标签: ios memory-management


    【解决方案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;
      }
      

      【讨论】:

        【解决方案3】:

        关键是想清楚下面的代码在做什么。为了清楚起见,我会完整地写出来:

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

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

        [[self dataToBeLoaded] release];
        

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

        安旺贴出的代码是正确的:

        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

        【讨论】:

        • 我知道setter是一个方法调用。在属性包含保留属性的情况下,也许我仍然不完全理解该设置器。我认为保留属性的设置器首先释放先前引用的实例(或者,用你的话来说,将其丢弃),然后分配传递的实例引用,然后保留它。现在传递的实例已经被 alloc/init 保留了一次。 -- 顺便说一下,如果被丢弃的引用实例的引用计数变为零,则不会在此过程中泄漏。我在你的解释中遗漏了什么吗?
        • 你对你的具体实现假设太多,而不是 API 承诺。您假设 setDataToBeLoaded: 分配一个 ivar 并保留它。您的 API 没有承诺这一点。 “保留”属性不是 API 中的承诺。这是一个提示,它恰好被@synthesize 使用(但@synthesize 与API 无关)。希望更清楚一点:永远不要发布你没有拥有的东西。调用dataToBeLoaded 不会获得返回值的所有权。该方法的名称中没有 new/copy/alloc。你不拥有它。
        • 你提到你没有泄漏。你其实是。您正在泄漏并且您正在过度释放,并且两者恰好平衡,因此您看不到 Instruments 中的泄漏。但是静态分析器应该同时注意两者。
        • 这与我在另一时间被告知的关于保留属性与合成设置器(以及自定义设置器)所采取的操作之间的关系相矛盾。我一直在合成设置器首先释放现有实例,然后将新值分配给 ivar,然后保留它的结构下工作。这听起来很方便,并且是使用属性而不是 ivars 的完美理由——因为该承诺已在 API 中作出。现在我不确定整个事情。我在哪里可以看到更完整的文档?
        • 顺便说一句,我有你的书。好工作。很有用。 (无耻的插头,但你应得的。)
        【解决方案4】:

        这只是意味着你正在释放一个你不拥有的对象。

        我会说直接使用实例 var 调用它,而不是使用 getter,但不确定这是否会修复您的分析警告。还有为什么不使用 [NSMutableData dataWithLength:1000];它是自动发布的,因此无需额外的发布调用(并且可能也会摆脱该警告!)

        其他解决方法:

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

        【讨论】:

          猜你喜欢
          • 2011-05-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多