Why Does "Try Catch" in Objective-C Cause Memory Leak

Why does try catch in Objective-C cause memory leak?

First of all: Exceptions have different semantics in Objective-C. An exception means that something went completely wrong because of a programming mistake and the further execution of the application is not useful. Terminate it! To handle "expected errors" (like insufficient user input or not responding servers et al.) use Cocoa's error handling pattern. (The reason for this is that exceptions seems to be convenient in many situation, but are very hard to handle in other situations, i. e. while object construction. Read about exceptions in C++. It is painful.)

To your Q: ARC adds additional code to handle memory management. This code has to be executed to handle the memory management, esp. to release objects. If an exception occurs before this is done, the control flow never reaches the release statement. Memory leaks.

- (void)method
{
id reference = …;
// Some ARC code to retain the object, reference points to.

@throw …

// reference loses its extent, because of method termination
// Some ARC code to release the object, reference points to.
}

If you have an exception, the method is left immediately and the ARC code and the end of the method to release the object is never executed. This is the leak.

You can change this behavior by compiling the source with -fobjc-arc-exceptions option.

http://clang.llvm.org/docs/AutomaticReferenceCounting.html#exceptions

This will add code to make ARC exception-safe causing a runtime penalty. But there is little reason to do so in Cocoa development, as explained at the beginning of this answer.

Can the try...catch mechanism be used to avoid memory crashes?

A try/catch block is there to catch an exception and stop it from propagating upwards in your callstack.

The idea goes that you catch it at the place where you know how to handle it, and then you get a chance to execute code in response to the exception.

It is not a magical solution that will prevent anything, it is just what I said above. What you do with the exception is what matters.

And a memory leak and a crash is not the same thing, it is rare that a program crashes due to memory leaks, unless it actually runs out of memory. A memory leak is rarely "fixable" after the fact. The correct, and usually only, way to fix a memory leak is to avoid it happening in the first place.

Also, yes, in a way you can keep your program from crashing by adding try/catch blocks all over, but the only thing you've succeeded in is to hide the crash from the user, and then let the program continue running. "Crashes" are not always safe to ignore, or rather, they are usually not safe to ignore.

If you are looking for some catch-all advice on how to avoid a program crashing, here's my advice:

  • Write a program that works correctly

I think we need to know more about what kinds of problems you're having, or you need to post a clearer question.

Objective C - What happens if an exception occurs before autorelease is called in the return statement?

but isn't this risking a memory leak if an exception occurs between allocating the memory and returning the object?

Yes, it is. The exception moves execution directly from the point where it's raised to the handler. This is explicitly called out in an example in Apple's Cocoa Exceptions Guide:

 - (void)doSomething {
NSMutableArray *anArray = [[NSMutableArray alloc] initWithCapacity:0];
[self doSomethingElse:anArray];
[anArray release];
}

The problem here is obvious: If the doSomethingElse: method throws an exception there is a memory leak.

So, how can you get around this? Well, the exception handling syntax includes an @finally block, which is run after the @try and after any @catch is made.

If an exception is possible in your -generateAutoreleaseObject, you could use a local exception handler with an @finally block to make sure that resources are cleaned up, and, if needed, also re-throw the exception. But see below about internal Cocoa code!

If generateAutoreleaseObject is as follows is there a memory leak?

Yes. It's also worth noting that ARC wouldn't necessarily help here. Again, control jumps from the point of throwing to the point of handling. ARC can't reasonably ensure that it can clean up in that situation by default. (You can see at that link, though, that there is a compiler option you can use.)

If so do the foundation classes that return autorelease objects handle this

Not really. No guarantees are made about the state of framework internals if your exception jumps back over stack frames involving them. This is hard to avoid, and that's why the mantra is that exceptions aren't for recoverable errors in Cocoa.

For more, here's two Apple runtime engineers on this topic: Objective-C ARC and longjmp

iphone - try, catch question

You can certainly have multiple lines in your try block. Example:

@try {
if (managedObjectContext == nil) {
actionMessage = @"accessing user recipe library";
[self initCoreDataStack];
}
actionMessage = @"finding recipes";
recipes = [self recipesMatchingSearchParameters];
actionMessage = @"generating recipe summaries";
summaries = [self summariesFromRecipes:recipes];
}
@catch (NSException *exception) {
NSMutableDictionary *errorDict = [NSMutableDictionary dictionary];
[errorDict setObject:[NSString stringWithFormat:@"Error %@: %@", actionMessage, [exception reason]] forKey:OSAScriptErrorMessage];
[errorDict setObject:[NSNumber numberWithInt:errOSAGeneralError] forKey:OSAScriptErrorNumber];
*errorInfo = errorDict;
return input;
} @catch (OtherException * e) {
....
} @finally {
// Any clean up can happen here.
// Finally will be called if an exception is thrown or not.
}

And a link to practical use of exceptions:

https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Exceptions/Tasks/HandlingExceptions.html

Objective-C try-catch - why does this compile? And why is the return different building debug vs release?

That is either a compiler bug or the "check control flow to make sure return value is present" option is turned off (if there is one).

The differing return value is because the behavior is undefined.

Basically, whatever happens to be in the slot -- likely a register, might be on the stack, depends on the ABI of the targeted CPU -- that holds the return value at the time the function returns will be used.

Without optimization, the compiler won't reuse registers and the stack; every variable gets its own space and it is preserved to the end of the function. With optimization, the compiler will reuse memory proactively, causing the behavior change. This is also why debugging optimized code is such a pain; 'p myVariable' might print something unexpected simply because 'myVariable' has been recycled.

How does one eliminate Objective-C @try @catch blocks like this?

Many people use a category on NSDictionary for these cases:

- (id)safeObjectForKey:(id)aKey
{
id obj = [self objectForKey:aKey];
if ([obj isKindOfClass:[NSNull class]])
{
return nil;
}

return obj;
}

You still need to make sure, that your dict is an actual dictionary instance.

Is there a performance penalty associated with calling a function within a @try block?

from doc

Zero-Cost @try Blocks

64-bit processes that enter a zero-cost @try block incur no
performance penalty. This is unlike the mechanism for 32-bit
processes, which calls setjmp() and performs additional “bookkeeping”.
However, throwing an exception is much more expensive in 64-bit
executables. For best performance in 64-bit, you should throw
exceptions only when absolutely necessary.

so no overhead for 64-bit processes

Objective C / iOS: Memory release with ARC (memory leak)

A few things could be going wrong:

  1. You might not actually have ARC enabled. You should double-check that. Simplest way is to throw in a -retain in your code and make sure that throws a compiler error.

  2. ARC doesn't necessarily prevent objects from entering the autorelease pool. It tries to catch that if it can, but it doesn't make guarantees. Notably, at -O0 (no optimization), it frequently does not prevent objects from entering the autorelease pool. This is most likely what's going on for you.

  3. Even at higher optimization levels, ARC-enabled code is still not guaranteed to catch autoreleases.

If you stick an @autoreleasepool{} inside of your for loop you'll find the memory usage should go away. Alternatively, instead of using [NSMutableString string], you could try [NSMutableString new], which doesn't use the autorelease pool at all* but should otherwise behave identically in ARC code.

* well, NSMutableString is free to use the autorelease pool internally if it wants to



Related Topics



Leave a reply



Submit