如何解决iphone开发中遇到的EXC_BAD_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 语句中失败。 有任何想法吗?
编辑:忽略添加原来是代码中存在问题的版本。
I'm trying to do a simple thing; read an image from the internet, save it to the app's documents directory on the iphone, and read it back from that file so that i can do other things with it later. Writing the file works fine but when i try to read it back i get an EXC_BAD_ACCESS error in GDB that i have no idea how to resolve. Here is what my code basically looks like:
-(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];
}
The code fails in the return statement when i try to initialize the UIImage from the file. Any ideas?
Edit: neglected to add a release that was the problem in the code originally.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
注意:这特别适用于非 ARC 内存管理。
因为这已经有很多观点并且检查的答案适当地指出“代码显示严重缺乏对 Objective-C 中内存管理如何工作的了解”,但没有人指出具体的错误,我想我应该添加一个涉及这些错误的答案。
关于调用方法,我们必须记住的基线级规则:
如果方法调用包含单词alloc、new、copy ,或保留,我们拥有所创建对象的所有权。如果我们拥有某个对象的所有权,我们就有责任释放它。
如果方法调用不包含这些单词,则我们不拥有所创建对象的所有权。¹如果我们不拥有对象的所有权,则释放这不是我们的责任,因此我们永远不应该这样做。
让我们看看OP的每一行代码:
我们开始了一个新方法。 通过这样做,我们开始了一个新的上下文,每个创建的对象都生活在其中。 请记住这一点。 下一行:
我们拥有
url
:其中的alloc一词告诉我们,我们拥有该对象的所有权,并且我们需要自己释放它。 如果我们不这样做,那么代码将泄漏内存。我们不拥有
路径
:不使用这四个魔法词,所以我们没有所有权,并且绝不能自己释放它。我们不拥有
documentsDirectory
:没有魔法词=没有所有权。返回几行,我们发现我们不拥有路径,因此当我们尝试访问不再存在的内容时,此版本将导致 EXC_BAD_ACCESS 崩溃。
我们不拥有
路径
:没有魔法=没有所有权。我们拥有
data
:其中的alloc一词告诉用户我们拥有该对象的所有权,并且我们需要自己释放它。 如果我们不这样做,那么代码将泄漏内存。以下两行不创建或释放任何内容。 然后是最后一行:
方法结束,因此变量的上下文已经结束。 查看代码,我们可以看到我们同时拥有
url
和data
,但没有释放其中任何一个。 因此,每次调用此方法时,我们的代码都会泄漏内存。NSURL
对象url
并不是很大,因此我们可能永远不会注意到泄漏,尽管它仍然应该被清理,但没有理由泄漏它。NSData
对象data
是一个 png 图像,并且可能非常大; 每次调用此方法时,我们都会泄漏对象的整个大小。 想象一下每次绘制表格单元格时都会调用此函数:用不了多久就会使整个应用程序崩溃。那么我们需要做什么来解决这些问题呢? 这非常简单,我们只需要在不再需要对象时立即释放它们,通常是在最后一次使用它们之后:
有些人喜欢在上下文结束时立即释放所有内容方法。 在这种情况下,
[url release];
和[data release];
将出现在右大括号}
之前。 我发现,如果我尽快释放它们,代码会更清晰,当我稍后回顾它时,我会清楚地知道我在哪里完成了对象。总结一下:我们拥有在方法调用中使用
alloc
、new
、copy
或retain
创建的对象,因此必须在上下文结束之前释放它们。 我们不拥有任何其他东西,也绝不能释放它们。``这四个词实际上并没有什么神奇之处,它们只是苹果公司创建相关方法的人员一贯使用的提醒。 如果我们为自己的类创建自己的初始化或复制方法,那么在适当的方法中包含单词alloc、new、copy或retain就是我们的责任,如果我们不在我们的名称中使用它们,那么我们我们需要记住所有权是否已经转移。
Note: This applies specifically to non-ARC memory management.
Since this has had so many views and the checked answer appropriately states that "code shows a severe lack of knowledge of how memory management works in Objective-C," yet no one has pointed the specific errors out, I figure I'd add an answer that touched on them.
The baseline-level rule that we have to remember about calling methods:
If the method call includes the words alloc, new, copy, or retain, we have ownership of the object created.¹ If we have ownership of an object, it's our responsibility to release it.
If the method call does not contain those words, we do not have ownership of the object created.¹ If we don't have ownership of an object, releasing it is not our responsibility, and we therefore should never do it.
Let's look at each line the OP's code:
We started a new method. In doing so we have started a new context in which each of the created objects lives. Keep this in mind for a bit. The next line:
We own
url
: the word alloc there tells us that we have ownership of the object and that we will need to release it ourselves. If we do not then the code will leak memory.We do not own
paths
: no use of the four magic words, so we do not have ownership and must never release it ourselves.We do not own
documentsDirectory
: no magic words = no ownership.Going back a couple of lines we see that we don't own paths, so this release will cause an EXC_BAD_ACCESS crash as we try to access something that no longer exists.
We do not own
path
: no magic words = no ownership.We own
data
: the word alloc there tells use that we have ownership of the object and that we will need to release it ourselves. If we do not then the code will leak memory.The following two lines don't create or release anything. Then comes the last line:
The method is over, so the context for the variables has ended. Looking at the code we can see that we owned both
url
anddata
, but didn't release either of them. As a result our code will leak memory every time this method is called.The
NSURL
objecturl
isn't very big, so it's possible that we might never notice the leak, though it should still be cleaned up, there's no reason to leak it.The
NSData
objectdata
is a png image, and could be very large; we are leaking the entire size of the object every time this method is called. Imagine this was called every time a table cell was drawn: it wouldn't take long at all to crash the whole app.So what do we need to do to fix the problems? It's pretty simple, we simply need to release the objects as soon as we no longer need them, commonly right after the last time they're used:
Some people prefer to release everything at once, right before the context closes at the end of the method. In that case both
[url release];
and[data release];
would appear right before the closing}
brace. I find that if I release them as soon as I can the code is clearer, making it clear when I go over it later exactly where I'm done with objects.To summarize: we own objects created with
alloc
,new
,copy
, orretain
in the method calls so must release them before the context ends. We don't own anything else and must never release them.¹There's nothing actually magical in the four words, they're just a consistently-used reminder by the folks at Apple who created the methods in question. If we create our own initialization or copying methods for a class of our own then the inclusion of the words alloc, new, copy, or retain in its appropriate methods is our responsibility, and if we don't use them in our names then we'll need to remember for ourselves whether ownership has passed.
对我有很大帮助的一件事是在 objc_exception_throw 上有一个断点。 每当我要抛出异常时,我都会遇到这个断点,并且可以调试堆栈链。 我只是在 iPhone 项目中始终启用此断点。
为此,请在 xcode 中转到左窗格“组和文件”底部附近并找到“断点”。 打开它并单击“项目断点”,在详细信息窗格(顶部)中,您将看到一个标有“双击符号”的蓝色字段。 双击它并输入“objc_exception_throw”。
下次抛出异常时,您将停止并在调试器中,您可以沿着堆栈链返回到导致异常的代码。
One thing that helps me a lot is to have a breakpoint on objc_exception_throw. Anytime I'm about to get an exception thrown, I hit this breakpoint and I can debug back up the stack chain. I just leave this breakpoint enabled all the time in my iPhone projects.
To do this, in xcode go to near the bottom of the left pane "Groups & Files" and find "Breakpoints". Open it and click on Project Breakpoints and in the detail pane (top), you'll see a blue field labeled "Double-Click for Symbol." Double-click on it and enter "objc_exception_throw".
Next time you throw an exception, you'll stop and in the debugger, you can walk back up the stack chain to your code that caused the exception.
您的代码表明严重缺乏对 Objective-C 中内存管理如何工作的了解。 除了您收到的 EXC_BAD_ACCESS 错误之外,不正确的内存管理还会导致内存泄漏,在 iPhone 这样的小型设备上,内存泄漏可能会导致随机崩溃。
我建议您仔细阅读:
Cocoa 内存管理编程指南简介
Your code shows a severe lack of knowledge of how memory management works in Objective-C. In addition to the EXC_BAD_ACCESS errors you're receiving, improper memory management also causes memory leaks which, on a small device like the iPhone, can lead to random crashes.
I recommend you give this a thorogh read:
Introduction to Memory Management Programming Guide for Cocoa
一定要快速回顾一下内存管理规则。 没有任何内容会导致您收到错误,但您会泄漏分配的所有对象。 如果您不理解保留/释放模式,则很可能在代码中的另一个位置没有正确保留对象,这就是导致 EXC_BAD_ACCESS 错误的原因。
另请注意,NSString 有处理文件系统路径的方法,您不必自己担心分隔符。
Definitely give memory management rules a quick review. Nothing jumps out that would cause the error you're getting, but you're leaking all those objects your allocating. If you don't understand the retain/release pattern, chances are there's another spot in your code where you're not retaining an object properly, and that's whats causing the EXC_BAD_ACCESS error.
Also note that NSString has methods for dealing with filesystem paths, you should never have to worry about the separator yourself.
但总的来说,如果您在代码中遇到 EXC_BAD_ACCESS,并且您一生都无法弄清楚原因,请尝试使用 NSZombie(不,我不是在开玩笑)。
在 Xcode 中,展开左侧的可执行文件部分。 双击与您的项目同名的列表(它应该是唯一的)。 在弹出的窗口中,转到“参数”,然后单击底部的加号按钮。 名称应为 NSZombieEnabled,值应设置为 YES,
这样,当您尝试访问已释放的对象时,您将获得更好的结果正在做。 找出错误后,只需将该值设置为NO即可。
希望这对某人有帮助!
In general though, if you're getting EXC_BAD_ACCESSs in your code and you can't for the life of you figure out why, try using NSZombie (no, I'm not kidding).
In Xcode, expand the Executables section on the left. Double click on the listing that has the same name as your project (it should be the only one). In the window that pops up, go to Arguments, and in the bottom portion, click the plus button. The name should be NSZombieEnabled and the value should be set to YES
This way, when you try to access a released object, you'll have a better of what you're doing. Just set the value to NO once you've figured out the bug.
Hope this helps someone!
当您对内存管理不善(即对象被过早释放或类似)时,就会出现这些错误。
尝试执行以下操作。
我花了很多时间进行实验,同时掌握释放/自动释放的概念。 有时也需要使用保留关键字(尽管在这种情况下可能不是)
另一个选择可能只是路径不存在,或者无法读取?
These errors occur when you are mismanaging memory (ie. an object is being released prematurely or similar)
Try doing something like the following..
I spent a lot of time experimenting whilst getting to grips with the concepts of release/autorelease. Sometimes the retain keyword needs to be played also (although probably not in this case)
Another option could be simply the path does not exist, or cannot be read from?
也许 initWithContentsOfFile 不接受路径参数? 浏览 UIImage 的不同 init 方法,我认为有一个不同的方法用于接受路径。
您可能还需要做一些更奇特的事情来开辟道路? 我记得用“捆绑”做过什么? 抱歉说得这么含糊,我只记得这些。
Perhaps, initWithContentsOfFile doesn't take a path argument? Browse around at the different init methods for UIImage, I think there's a different one for accepting a path.
There also might be something fancier you have to do for making a path? I remember doing something with "bundles"? Sorry to be so vague, it's all I remember offhand.
去掉路径上的斜杠,并确保它位于项目中。 它是否在该目录中并不重要,但必须将其添加到项目中才能访问它。
take the slash off the path, and make sure it is in the project. doesn't matter if it is in that dir, but it has to be added to the project for you to access it.