如何解决iphone开发中遇到的EXC_BAD_ACCESS错误

时间:2009-03-03 16:55:24

标签: iphone objective-c cocoa-touch file-access

我想做一件简单的事;从互联网上读取图像,将其保存到iphone上的应用程序文档目录中,然后从该文件中读回来,以便我以后可以用它做其他事情。编写文件工作正常,但当我尝试读回来时,我在GDB中得到一个EXC_BAD_ACCESS错误,我不知道如何解决。这是我的代码基本上是什么样的:

-(UIImage *) downloadImageToFile {
NSURL * url = [[NSURL alloc] initWithString: self.urlField.text];

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

NSString *documentsDirectory = [paths objectAtIndex:0];

[paths release]
NSString * path = [documentsDirectory stringByAppendingString:@"/testimg.png"];

NSData * data = [[NSData alloc] initWithContentsOfURL:url];

[data writeToFile:path atomically:YES];

return [[UIImage alloc] initWithContentsOfFile:path];
}

当我尝试从文件初始化UIImage时,代码在return语句中失败。有什么想法吗?

修改:忽略添加最初代码中存在问题的版本。

8 个答案:

答案 0 :(得分:46)

注意:这特别适用于非ARC 内存管理。

由于这有很多观点,而且经过检查的答案恰如其分地表明“代码显示内存管理在Objective-C中的工作原理严重缺乏”,但没有人指出具体的错误,我想我d添加一个触及它们的答案。

我们必须记住调用方法的基线级规则:

  • 如果方法调用包含分配复制保留,我们拥有所创建对象的所有权.¹如果我们拥有对象的所有权,我们有责任释放它。

  • 如果方法调用包含这些字词,我们就没有所创建对象的所有权.¹如果我们拥有所有权对象,释放它是我们的责任,因此我们永远不应该这样做。

让我们看看OP代码的每一行:

-(UIImage *) downloadImageToFile {

我们开始了一种新方法。在这样做的过程中,我们开始了一个新的上下文,其中每个创建的对象都存在。请记住这一点。下一行:

    NSURL * url = [[NSURL alloc] initWithString: self.urlField.text];

我们拥有url alloc 这个词告诉我们我们拥有该对象的所有权,我们需要自己发布它。如果我们不这样做,代码就会泄漏内存。

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

我们不拥有paths:不使用这四个魔术词,所以我们没有所有权,也绝不能自己释放。

    NSString *documentsDirectory = [paths objectAtIndex:0];

我们不拥有documentsDirectory:没有魔术词=没有所有权。

    [paths release]

回过头几行,我们发现我们没有路径,因此当我们尝试访问不再存在的内容时,此版本会导致EXC_BAD_ACCESS崩溃。

    NSString * path = [documentsDirectory stringByAppendingString:@"/testimg.png"];

我们不拥有path:没有魔术词=没有所有权。

    NSData * data = [[NSData alloc] initWithContentsOfURL:url];

我们拥有data alloc 这个词告诉我们我们拥有对象的所有权,我们需要自己发布它。如果我们不这样做,代码就会泄漏内存。

以下两行不会创建或发布任何内容。然后是最后一行:

}

方法结束了,因此变量的上下文已经结束。查看代码,我们可以看到我们同时拥有urldata,但没有释放它们。因此,每次调用此方法时,我们的代码都会泄漏内存。

NSURL对象url不是很大,所以我们可能永远不会注意到泄漏,尽管它仍然应该被清除,没有理由泄漏它。

NSData对象data是png图像,可能非常大;每次调用此方法时,我们都会泄漏对象的整个大小。想象一下,每次绘制一个表格单元格时都会调用它:整个应用程序崩溃不会花费很长时间。

那么我们需要做些什么来解决这些问题呢?这很简单,我们只需要在不再需要它们时立即释放它们,通常是在它们最后一次使用之后:

-(UIImage *) downloadImageToFile {

    // We own this object due to the alloc
    NSURL * url = [[NSURL alloc] initWithString: self.urlField.text];

    // We don't own this object
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

    // We don't own this object
    NSString *documentsDirectory = [paths objectAtIndex:0];

    //[paths release] -- commented out, we don't own paths so can't release it

    // We don't own this object
    NSString * path = [documentsDirectory stringByAppendingString:@"/testimg.png"];

    // We own this object due to the alloc
    NSData * data = [[NSData alloc] initWithContentsOfURL:url];

    [url release]; //We're done with the url object so we can release it

    [data writeToFile:path atomically:YES];

    [data release]; //We're done with the data object so we can release it

    return [[UIImage alloc] initWithContentsOfFile:path];

    //We've released everything we owned so it's safe to leave the context
}

有些人喜欢在方法结束时关闭上下文之前立即发布所有内容。在这种情况下,[url release];[data release];都会在结束}括号之前出现。我发现,如果我尽快释放它们,代码就会更清晰,那么当我稍后查看完所有对象的地方时,我会清楚地说明它。

总结一下:我们拥有在方法调用中使用allocnewcopyretain创建的对象,因此必须在上下文结束之前释放它们。我们没有其他任何东西,绝不能释放它们。


¹这四个词中没有任何神奇的东西,它们只是苹果公司创造有关方法的人们常用的提醒。如果我们为自己的类创建自己的初始化或复制方法,那么在其适当的方法中包含单词alloc,new,copy或retain是我们的责任,如果我们不在我们的名称中使用它们,那么我们我们需要自己记住所有权是否已经过去了。

答案 1 :(得分:10)

对我有很大帮助的一件事是在objc_exception_throw上有一个断点。任何时候我都要抛出一个异常,我点击了这个断点,我可以调试堆栈链。我只是在我的iPhone项目中一直启用此断点。

为此,在xcode中转到左侧窗格“Groups& Files”的底部附近并找到“Breakpoints”。打开它并单击Project Breakpoints,然后在详细信息窗格(顶部)中,您将看到一个标有“Double-Click for Symbol”的蓝色字段。双击它并输入“objc_exception_throw”。

下次抛出异常时,您将停止并在调试器中,将堆栈链向上移回导致异常的代码。

答案 2 :(得分:8)

您的代码显示对Objective-C中内存管理的工作方式缺乏了解。除了您收到的EXC_BAD_ACCESS错误之外,不正确的内存管理还会导致内存泄漏,在iPhone等小型设备上可能会导致随机崩溃。

我建议你给这个读一遍:

Introduction to Memory Management Programming Guide for Cocoa

答案 3 :(得分:1)

绝对快速审核内存管理规则。什么都没有跳出来会导致你得到的错误,但是你正在泄漏所有分配的对象。如果你不理解保留/释放模式,你的代码中可能还有另一个你没有正确保留对象的地方,那就是导致EXC_BAD_ACCESS错误的原因。

另请注意,NSString具有处理文件系统路径的方法,您自己永远不必担心分隔符。

答案 4 :(得分:1)

一般情况下,如果您在代码中获得了EXC_BAD_ACCESS并且在生活中无法找出原因,请尝试使用NSZombie(不,我不是在开玩笑)。

在Xcode中,展开左侧的“可执行文件”部分。双击与项目同名的列表(它应该是唯一的列表)。在弹出的窗口中,转到Arguments,在底部,单击加号按钮。名称应为 NSZombieEnabled ,值应设置为 YES

这样,当您尝试访问已发布的对象时,您将更好地了解自己正在做的事情。一旦找到错误,只需将值设置为 NO 即可。

希望这有助于某人!

答案 5 :(得分:0)

当您对内存管理不善时(即,某个对象过早或类似地被释放)会发生这些错误。

尝试执行以下操作..

UIImage *myImage = [[UIImage alloc] initWithContentsOfFile:path];
return [myImage autorelease];

我花了很多时间进行实验,同时掌握释放/自动释放的概念。有时候还需要播放retain关键字(尽管可能不是这种情况)

另一种选择可能只是路径不存在,或者无法读取?

答案 6 :(得分:-1)

或许,initWithContentsOfFile不接受路径参数?浏览UIImage的不同init方法,我认为接受路径的方法不同。

你可能还有更多的东西可以做一条路径吗?我记得用“捆绑”做点什么吗?很抱歉这么模糊,这是我记得的一切。

答案 7 :(得分:-2)

从路径中删除斜杠,并确保它在项目中。如果它在那个目录中并不重要,但必须将它添加到项目中才能访问它。