Hello!
Today I’ll tell you about one thing related to Objective-C exceptions when using ARC.
I’ve run into this issue just recently. Hope this article will help you to take these peculiarities into account when writing your own app.
Let’s have a look at this piece of code first:
@try {
NSData *data = [NSData dataWithContentsOfFile:dataPath];
//some code that produces exception
@throw([NSException exceptionWithName:@"MyException" reason:nil userInfo:nil]);
}
@catch (NSException *exception) {
NSLog(@"handled exception");
}
Can you notice any memory leak here? Take your time and check via Instruments. Seems like there’s no memory leak. But in reality, NSData object is “leaking”!
Let’s look into this Clang doc:
“By default in Objective C, ARC is not exception-safe for normal releases:
- It does not end the lifetime of __strong variables when their scopes are abnormally terminated by an exception.
- It does not perform releases which would occur at the end of a full-expression if that full-expression throws an exception.”
Just wonder how this behavior can be reasoned out. The doc gives an answer:
“The standard Cocoa convention is that exceptions signal programmer error and are not
intended to be recovered from. Making code exceptions-safe by default would impose severe runtime and code size penalties on code that typically does not actually care about exceptions safety. Therefore, ARC-generated code leaks by default on exceptions, which is just fine if the process is going to be immediately terminated anyway.”
That is, Clang developers assume that Objective-C programs will not anyway recover from an exception.
Happily or not, we can change this behavior. A program may be compiled with the option -fobjc-arc-exceptions to enable the behavior mentioned above, or with the option -fno-objc-arc-exceptions to explicitly disable it.
By the way, in Objective-C++, -fobjc-arc-exceptions is enabled by default.
This issue is rather critical for iOS apps, because if your app reserves too much memory, the OS may force quit it.
Note: The exact memory amount depends on the iOS version and your device type.
So if your app uses large amount of memory and, besides, creates and deletes objects, this flag might be useful to reduce the total memory usage by your app.
And now let’s look back to the moment when I said that we can fix this issue, fortunately or not. So why wasn’t this option enabled by default by Clang team?
Clang documentation unveils the mystery:
“ARC does end the lifetimes of __weak objects when an exception terminates their scope
unless exceptions are disabled in the compiler.
Rationale
The consequence of a local __weak object not being destroyed is very likely to be corruption of the Objective-C runtime, so we want to be safer here. Of course, potentially massive leaks are about as likely to take down the process as this corruption is if the program does try to recover from exceptions.”
When developing Persona Mail, I became reassured that this option was disabled by default on purpose, when my app started crashing all of a sudden. So I had to disable it, too.
Let’s sum up:
- When raising and catching exception in your Objective-C app which uses ARC, memory leaks can occur.
- Clang has -fobjc-arc-exceptions option to avoid this.
Though it should be used with caution as you can get invalid pointer to the object and EXC_BAD_ACCESS signal when trying to call it.
Even if you follow Clang advice and do not enable -fobjc-arc-exceptions option, it’s good to be aware of such ARC issue.
See you.
Subscribe here:
https://www.facebook.com/develtima
https://twitter.com/develtima