释放先前释放的对象问题

Posted

技术标签:

【中文标题】释放先前释放的对象问题【英文标题】:release of previously deallocated object issue 【发布时间】:2010-11-27 12:38:10 【问题描述】:

我有一个函数用于从 csv 文件中读取一行。 但是我得到了一个先前释放对象错误的释放,或者有时它是“双重释放”错误。

我试图根据错误内存地址来追踪是哪个对象导致了这个错误,但是我没有这样做。

代码如下:

    @interface CSVParser : NSObject 
    NSString *fileName;
    NSString *filePath;
    NSString *tempFileName;
    NSString *tempFilePath;

    //ReadLine control
    BOOL isFirstTimeLoadFile;
    NSString *remainContent;


@property(nonatomic,retain) NSString *fileName;
@property(nonatomic,retain) NSString *filePath;
@property(nonatomic,retain) NSString *tempFileName;
@property(nonatomic,retain) NSString *tempFilePath;

@property(nonatomic,retain) NSString *remainContent;

-(id)initWithFileName:(NSString*)filename;

-(BOOL)checkAndCopyFile:(NSString *)filename;
-(BOOL)checkAndDeleteTempFile;
-(NSString*)readLine;
-(NSArray*)breakLine:(NSString*)line;

@end

@implementation CSVParser

@synthesize fileName;
@synthesize filePath;
@synthesize tempFileName;
@synthesize tempFilePath;

@synthesize remainContent;

-(id)initWithFileName:(NSString *)filename
    //ReadLine control
    isFirstTimeLoadFile = TRUE;

    self.fileName = filename;
    self.tempFileName = [[NSString alloc] initWithFormat:@"temp_%@",fileName];
    NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentDir = [documentPaths objectAtIndex:0];
    self.filePath = [documentDir stringByAppendingPathComponent:fileName];
    self.tempFilePath = [documentDir stringByAppendingPathComponent:tempFileName];
    if ([self checkAndCopyFile:fileName]) 
        return self;
    else 
        return @"Init Failure";
    



-(BOOL)checkAndCopyFile:(NSString *)filename
    BOOL isFileExist;
    NSError *error = nil;
    NSFileManager *fileManger = [NSFileManager defaultManager];
    isFileExist = [fileManger fileExistsAtPath:filePath];
    if (isFileExist) 
        //Create a temp file for reading the line.
        [fileManger copyItemAtPath:filePath toPath:tempFilePath error:&error];
        return TRUE;
    else 
        return FALSE;
    


-(NSString*)readLine
    NSError *error = nil;
    //Read the csv file and save it as a string
    NSString *tempFirstLine = [[[NSString alloc] init] autorelease];
    NSString *stringFromFileAtPath = [[NSString alloc] init];
    if (isFirstTimeLoadFile) 
        NSLog(@"Into First Time");
        stringFromFileAtPath = [NSString stringWithContentsOfFile:tempFilePath 
                                                         encoding:NSUTF8StringEncoding 
                                                            error:&error];
        isFirstTimeLoadFile = FALSE;
    else 
        NSLog(@"Not First Time");
        NSLog(@"Not First Time count:%d",[remainContent retainCount]);
        stringFromFileAtPath = remainContent;
        remainContent = nil;
    
    if ([stringFromFileAtPath isEqualToString:@""]) 
        [stringFromFileAtPath release];
        return @"EOF";
    

    //Get the first line's range
    NSRange firstLineRange = [stringFromFileAtPath rangeOfString:@"\n"];
    //Create a new range for deletion. This range's lenght is bigger than the first line by 1.(Including the \n)
    NSRange firstLineChangeLineIncludedRange;
    if (stringFromFileAtPath.length > 0 && firstLineRange.length == 0) 
        //This is the final line.
        firstLineRange.length = stringFromFileAtPath.length;
        firstLineRange.location = 0;
        firstLineChangeLineIncludedRange = firstLineRange;
    else 
        firstLineRange.length = firstLineRange.location;
        firstLineRange.location = 0;
        firstLineChangeLineIncludedRange.location = firstLineRange.location;
        firstLineChangeLineIncludedRange.length = firstLineRange.length + 1;
    
    //Get the first line's content
    tempFirstLine = [stringFromFileAtPath substringWithRange:firstLineRange];
    remainContent = [stringFromFileAtPath stringByReplacingCharactersInRange:firstLineChangeLineIncludedRange withString:@""];

    [stringFromFileAtPath release];
    error = nil;
    return tempFirstLine;

下面的代码展示了我如何使用上面的类:

CSVParser *csvParser = [[CSVParser alloc] initWithFileName:@"test.csv"];
BOOL isFinalLine = FALSE;

while (!isFinalLine) 
    NSString *line = [[NSString alloc] init];
    line = [csvParser readLine];
    if ([line isEqualToString:@"EOF"]) 
        isFinalLine = TRUE;
    
    NSLog(@"%@",line);
    [line release];

[csvParser release];

如果我运行代码并完成 csv 解析,应用程序的 main 函数会在尝试释放自动释放池时给我双重释放错误。"* __NSAutoreleaseFreedObject(): release of先前释放的对象 (0x6a26050) 被忽略"

NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; int retVal = UIApplicationMain(argc, argv, nil, nil);

有人可以帮我解决这个问题吗? 谢谢! [池释放];

【问题讨论】:

我可以推荐一个功能更全面的 CSV 解析器吗? github.com/davedelong/CHCSVParser 【参考方案1】:

不要使用 -retainCount。

对象的绝对保留计数是没有意义的。

您应该调用release 的次数与您导致对象被保留的次数完全相同。不会少(除非您喜欢泄漏),当然也不会更多(除非您喜欢崩溃)。

详情请参阅Memory Management Guidelines。


你的代码有几个问题:

您没有遵循正确的init 模式。你应该在某个地方有一个self = [super init...]; if (self) ...

tempFileNameretain 属性,您将其分配为 alloc/init 的结果。会泄露的。

不可变的空字符串 ([[NSString alloc] init]) 几乎没有用处。而且,事实上,stringFromFileAtPath 正在被泄露(从技术上讲——在实现细节方面,有一个空的不可变单例字符串,因此没有真正的泄露,但是......仍然......)

最后,崩溃:您的readLine 方法正确返回了一个自动释放的对象。然而,您的while() 循环消耗readLine 的返回值也是releaseing 该返回值,导致双重释放并尝试释放已释放的内容。

您应该“构建和分析”您的代码。我敢打赌 llvm 静态分析器会识别出我上面提到的大部分问题(如果不是全部的话)(可能还有一些我错过了)。


使用分析器构建时,您是否在构建窗口中选择了“所有消息”或“仅分析器问题”?因为,查看代码,我很惊讶分析器没有发现stringFromFileAtPath 的明显问题。

摘录代码,您有以下几行操纵stringFromFileAtPath

NSString *stringFromFileAtPath = [[NSString alloc] init];
....
stringFromFileAtPath = [NSString stringWithContentsOfFile:tempFilePath 
                                                 encoding:NSUTF8StringEncoding 
                                                     error:&error];
....
stringFromFileAtPath = remainContent;
....
[stringFromFileAtPath release];

remainContent 的设置者为:

remainContent = [stringFromFileAtPath stringByReplacingCharactersInRange:firstLineChangeLineIncludedRange
                                                              withString:@""];

您正在释放一个自动释放的对象。 内存不断增加,你是如何衡量它的?不要使用 Activity Monitor,因为它对开发人员几乎毫无用处,就像 retainCount 具有误导性一样。使用仪器。

【讨论】:

嗨,bbum,非常感谢您的回复。不幸的是,自动释放的对象不是崩溃的根本原因。我将该对象注释掉并返回一个不是自动释放对象的字符串对象,但它仍然崩溃。而且我也尝试了构建和分析,但我得到的只是“构建成功”信息。我发现崩溃是由 stringFromFileAtPath 对象引起的,如果我在 readLine 函数结束时不释放它,应用程序不会崩溃,但内存一直在上升。这个应用程序会处理一个比较大的 csv 文件,所以这是不可接受的。【参考方案2】:

您的 tempFirstLine NSString 对象使用 autorelease 声明,并作为您的 NSString 行返回,然后被释放。

试试这个:

while (!isFinalLine) 
NSString *line = [csvParser readLine];
if ([line isEqualToString:@"EOF"]) 
    isFinalLine = TRUE;

NSLog(@"%@",line);

【讨论】:

还是不行。我不认为这是由 tempFirstLine 引起的。 tempFirstLine 是一个自动释放对象,但是我分配了一个新对象 *line 来获取字符串,所以如果我释放 *line 对象,它不会影响 tempFirstLine 对象。【参考方案3】:

替换这个:

NSString *stringFromFileAtPath = [[NSString alloc] init];

用这个:

NSString *stringFromFileAtPath = nil;

并摆脱 [stringFromFileAtPath release] 语句。

第一行创建了一个指向一个你从不使用的新字符串对象的指针,因为你立即用一个指向其他地方的字符串对象的指针覆盖了这个指针,你不需要释放它,因为你不拥有它们/没有创造它们。既然你要释放它们,你就会崩溃。

tempFirstLine 也犯了同样的错误。

【讨论】:

以上是关于释放先前释放的对象问题的主要内容,如果未能解决你的问题,请参考以下文章

WARN - 获取ImportedKeys 失败游标先前已被释放且不可用

在自动释放池 内释放创建的自动释放对象 [关闭]

UIViewController 在解除先前呈现的模态视图控制器后被释放

如何找到带有自动释放消息的对象?

iPhone 开发 - 释放一个自动释放的对象

释放自动释放对象不会使我的应用程序崩溃,为啥?