1

我花了最后一天寻找一个动态存储问题,最后我不知道发生了什么,除了我必须误解/错过了一些关于子类化 NSString 的事情。这是一个存在问题的大量缩减和大量检测的样本:

IDStringBug.h 包含:

#import <Foundation/NSArray.h>
#import <Foundation/NSString.h>
/*==================================*/
@interface IDStringBug:NSString {
  NSString      *_backingStore;
  NSArray   *path;
}
- (NSArray*) path;
- (void)     dealloc;
- (NSUInteger) length;
-(id)          initWithString:  (NSString*)  string;
-(unichar)     characterAtIndex:(NSUInteger) index;
@end

IDStringBug.m 包含:

#include <stdio.h>
#import "IDStringBug.h"

@implementation IDStringBug
- (NSArray*) path {
  printf ("Return ptr to IDString: %s\n", [_backingStore cString]);
  return path;}

- (void) dealloc {
  printf ("Release IDString: %s\n", [_backingStore cString]);
  printf ("Path count is %d\n", (int) [path retainCount]);
  [_backingStore  release];
  printf ("Apres _backinstore\n");
  printf ("Path count is %d\n", (int) [path retainCount]);
  [path release];
  printf ("After path release, done but for super\n");
  [super dealloc];
}

-(id)initWithString:(NSString*)string {
  if ((self = [self init])) {
    _backingStore = [[NSString stringWithString:string] copy];
  }
  path    = [_backingStore componentsSeparatedByString: @"."];

  printf ("Path count is %d\n", (int) [path retainCount]);
  return self;
}

-(NSUInteger) length {
  return [_backingStore length];
}
-(unichar)characterAtIndex:(NSUInteger)index {
  return [_backingStore characterAtIndex:index];
}
@end

bug.m 包含:

#include    <stdio.h>
#include    <Foundation/NSAutoreleasePool.h>
#import "IDStringBug.h"

int main(int argc, char* argv[]) {
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  IDStringBug *myids      = [IDStringBug stringWithString: @"a.b.c"];

  printf ("Path count is %d\n", (int) [[myids path] retainCount]);
  printf ("pool=%d\n",          (int) [pool autoreleaseCount]);

  [pool release];
}

输出是:

$ ./bug
Path count is 1
Return ptr to IDString: a.b.c
Path count is 1
pool=7
Release IDString: a.b.c
Segmentation fault (core dumped)
4

2 回答 2

6

这个答案不能直接解决您的问题,但会间接解决它并导致更易于维护的设计模式。

不要继承 NSString。

相反,使用组合。

@interface PathString:NSObject
@property(copy) NSString *stringValue;
@property(strong) NSArray *pathValue;
... etc ...
@end

实际的崩溃是这样的:

  path    = [_backingStore componentsSeparatedByString: @"."];

该方法返回一个自动释放的对象,并在池耗尽时将其释放,留下一个悬空引用。

正如其他人所提到的,retainCount完全没用

请注意,这很奇怪:

_backingStore = [[NSString stringWithString:string] copy];

那应该只是:

_backingStore = [string copy]; 

您的代码在技术上复制了两次字符串。我从技术上说,因为——由于实现细节——_backingStore最终会指向string(假设string是一个NSString而不是一个NSMutableString.

在 NeXT 时代,我是一个非常熟悉的人,但大部分时间都在离开或只使用 Objc 基础。

啊哈!我也是,我在 1989 年开始进行 ObjC 编程。

这可以解释你从哪里来!

而不是retainCount,这样的问题很容易使用僵尸进行调试。您可以在 Xcode 中方案的选项窗格中打开它。

“whentouseretaincount.com”网站链接到我写的一篇关于保留计数的文章。您可能会发现它很有趣,因为它通常还阐明了内存管理的一些细节。

Apple 文档到 Linux GnuStep 世界

在您的问题中也需要注意这一点。GNUStep 与 Apple 的产品很相似,但更接近于 OpenStep 世界。我不记得 GNUStep 是否具有僵尸检测功能,但我怀疑它会。Linux 还有其他非常强大的内存调试工具。

retainCount仍然会充满脆弱性,但在处理单线程、命令行、工具时它会更加稳定。不过,您仍然需要注意自动发布的内容。

于 2013-07-14T19:00:46.113 回答
2

调用componentsSeparatedByString:返回一个NSArray已自动释放的。这意味着它的保留计数为 1,但一旦自动释放池耗尽,该计数就会减少。将它与 IDStringBug dealloc 中的调用结合起来release,您会看到该数组被释放的次数过多。

事实上,在调用 IDStringBug dealloc 时,路径数组已经被释放。因此,当您尝试确定保留计数时(调用[path retainCount]您正在尝试访问不再存在的对象。

于 2013-07-14T17:17:43.010 回答