4

When you catch an exception in an ObjC @catch block, what is the lifecycle of that exception object? I know I can safely use it inside the block, but what if I want to use it again after the block, like this?

NSException * exception = nil;
@try {
    // do something risky
} @catch(NSException * e) {
    exception = e;
}

if (exception) {
    NSLog(@"Caught exception: %@", exception);
}

Can I safely stash the reference into another local? Should I be retain, autoreleaseing it for safety? Can I retain it and hold onto it indefinitely?

(It does seem to work OK if I assign to the local, or retain and use later, but the docs don't really discuss where this object "comes from" in terms of ownership, or if it's special, so I was looking for more clarity.)

Ben Zotto
  • 70,108
  • 23
  • 141
  • 204

4 Answers4

2

http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Exceptions/Tasks/HandlingExceptions.html

Almost all NSException objects (and other types of exception objects) are created autoreleased, which assigns them to the nearest (in scope) autorelease pool. When that pool is released, the exception is destroyed.

Also, I'm pretty sure that somewhere in the memory programming guide, they mention that methods without new or alloc or copy in their names always return autoreleased objects by convention. NSException's methods qualify for that.

Slightly related (not NSException but NSError):

If you create an NSError object with initWithDomain:code:userInfo:, you should send autorelease to it before you return it to the caller.

http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ErrorHandlingCocoa/CreateCustomizeNSError/CreateCustomizeNSError.html

user123444555621
  • 148,182
  • 27
  • 114
  • 126
  • Good reference. There is more detailed discussion on that page regarding the behavior of autorelease pools around exceptions too, which is the sticky case I was largely interested in. – Ben Zotto Dec 11 '11 at 22:58
  • Still not sure what "almost all" means there. – user123444555621 Dec 12 '11 at 12:19
  • Maybe it just means by convention. Certainly the `[NSException raise...` will be autoreleased, but you could of course do this: `id foo = [[Foo alloc] init]; @throw foo;` – Ben Zotto Dec 12 '11 at 16:11
  • I'm awarding you the bounty for getting the most detailed answer here. I was hoping to draw a fully authoritative discussion of the stack unwind in the runtime and its implications, so I'll leave it as unanswered for now. Thanks! – Ben Zotto Dec 12 '11 at 16:14
1

@catch blocks do absolutely nothing for lifecycle. The implicit contract here is an NSException object that is -raise'd or @thrown should be an autoreleased object. This means that in the @catch block, the NSException that you are given is an autoreleased object, just like any autoreleased object you might get from a method call. You can safely stash it in a local and reference it after the @catch block.

Lily Ballard
  • 182,031
  • 33
  • 381
  • 347
  • How does this play (if at all) with nested autorelease pools? I get that at the site of the throw, you'd throw an autoreleased object, but what if it ends up getting thrown up through multiple nested autorelease pools? What if there are `drain`s in the `@finally` blocks on the way up? Is this potential issue just ignored by the runtime assuming it works out fine? Or is something special happening in the runtime here? – Ben Zotto Dec 10 '11 at 18:01
  • @quixoto: If it gets thrown through autorelease pools, those pools get dropped on the floor and don't drain. However, due to the way autorelease pools work, once an outer pool drains, all of the inner pools that were dropped on the floor also drain. However yes, if you have `drain`s inside `@finally` blocks, then I'm not really sure how the exception behaves. It's possible it's retained by the system while it's being thrown, I don't really know. – Lily Ballard Dec 11 '11 at 00:11
0

NSException inherits from NSObject, so you can probably do all the typical things one would do with any other Objective C object.

However, I'd recommend not doing anything with it outside of your thread. This O'Reilly article about Exceptions suggests:

Do not use a release or an autorelease message to dispose an NSException. All instances of NSException are placed in the main autorelease pool. Manually disposing an instance will result in a SIGSEGV error.

Do not use a retain message to preserve an NSException. It will prevent the autorelease pool from disposing the instance. This will only result in a subtle memory leak.

... and some other useful helpful hints on these objects.

Michael Dautermann
  • 88,797
  • 17
  • 166
  • 215
  • 1
    That advice from the book seems useless. It's just describing the normal memory management policy. The problem it describes is if you release without retaining, or retain without releasing (or autoreleasing). If you do both it should not be a problem I guess. – morningstar Dec 03 '11 at 05:23
  • The only relevant line here seems to be "All instances of NSException are placed in the main autorelease pool." I wish there were a pointer to an Apple doc that said this, and was explicit about how this relates to the stack unwind from the exception. (E.g. is it magically put in the top autorelease pool at the point it's caught?, or where it's thrown, assuming that that pool doesn't get cleaned up) – Ben Zotto Dec 04 '11 at 17:06
  • i'm pretty sure it means the current autoreleasepool – hooleyhoop Dec 09 '11 at 15:45
0

NSException adopts NSCopying (and NSCoding fwiw). If you are in a case of questionable lifetime and want to make that explicit, a copy would be ideal.

I'm stopping there - unwinding and cocoa idioms flow against each other.

justin
  • 104,054
  • 14
  • 179
  • 226