11

Hey all. I've been reading up on Apple's suggestions for when/where/how to use NSError versus @try/@catch/@finally. Essentially, my impression is that Apple thinks it best to avoid the use of exception handling language constructs except as a mechanism for halting program execution in unexpected error situations (maybe someone could give an example of such a situation?)

I come from Java, where exceptions are the way to go when one wants to handle errors. Admittedly, I'm still in the Java thoughtspace, but I'm slowly coming to grips with all that NSError has to offer.

One thing I'm hung up on is the task of cleaning up memory when an error occurs. In many situations (e.g. using C, C++ libraries, CoreFoundation, etc..) you have a lot of memory cleanup that needs to be done before breaking out of a function due to an error.

Here's an example I cooked up that accurately reflects the situations I've been encountering. Using some imaginary data structures, the function opens up a file handle and creates a 'MyFileRefInfo' object which contains information about what to do with the file. Some stuff is done with the file before the file handle is closed and the memory for the struct freed. Using Apple's suggestions I have this method:

- (BOOL)doSomeThingsWithFile:(NSURL *)filePath error:(NSError **)error
{
  MyFileReference inFile; // Lets say this is a CF struct that opens a file reference
  MyFileRefInfo *fileInfo = new MyFileRefInfo(...some init parameters...);

  OSStatus err = OpenFileReference((CFURLRef)filePath ,&inFile);

  if(err != NoErr)
  {
    *error = [NSError errorWithDomain:@"myDomain" code:99 userInfo:nil];
    delete fileInfo;
    return NO;
  }

  err = DoSomeStuffWithTheFileAndInfo(inFile,fileInfo);

  if(err != NoErr)
  {
    *error = [NSError errorWithDomain:@"myDomain" code:100 userInfo:nil];
    CloseFileHandle(inFile); // if we don't do this bad things happen
    delete fileInfo;
    return NO;
  }      

  err = DoSomeOtherStuffWithTheFile(inFile,fileInfo);

  if(err != NoErr)
  {
    *error = [NSError errorWithDomain:@"myDomain" code:101 userInfo:nil];
    CloseFileHandle(inFile); // if we don't do this bad things happen
    delete fileInfo;
    return NO;
  }      

  CloseFileHandle(inFile);
  delete fileInfo;
  return YES;

}

Now.. my Java logic tells me that it would be better to set this up as a try/catch/finally structure and put all the calls to close the file handle and free memory in the finally block.

Like so..

    ...

    @try
    {
      OSStatus err = OpenFileReference((CFURLRef)filePath ,&inFile);
      if(err != NoErr)
      {
        ... throw some exception complete with error code and description ...
      }

      err = DoSomeStuffWithTheFileAndInfo(inFile,fileInfo);

      if(err != NoErr)
      {
         ... throw some exception ...
      }

      ... etc ...        
}
@catch(MyException *ex)
{
        *error = [NSError errorWithDomain:@"myDomain" code:[ex errorCode] userInfo:nil];
        return NO;
}
@finally
{
        CloseFileHandle(inFile); // if we don't do this bad things happen
        delete fileInfo;
}
return YES;

Am I crazy in thinking that this is a much more elegant solution with less redundant code? Did I miss something?

Chris Hanson
  • 54,380
  • 8
  • 73
  • 102
  • The example may have been simplified a bit, but don't forget to check that error is not NULL before you assign it, since the convention is to send NULL in place of the error variable when you don't care about errors. If you dereference NULL in this case, you will cause a bus fault (it's not like messaging a nil object). – Jason Coco Jan 05 '10 at 21:00

4 Answers4

17

Daniel's answer is correct, but this question deserves a rather more blunt answer.

Throw an exception only when a non-recoverable error is encountered.

Use NSError when communicating error conditions that may be recovered from.

Any exception that is thrown through a frame in Apple's frameworks may result in undefined behavior.

There is an Exceptions programming topic document available in the dev center.

Maxim Kholyavkin
  • 4,463
  • 2
  • 37
  • 82
bbum
  • 162,346
  • 23
  • 271
  • 359
12

Essentially, my impression is that Apple thinks it best to avoid the use of exception handling language constructs except as a mechanism for halting program execution in unexpected error situations (maybe someone could give an example of such a situation?)

That's not quite my impression. I thought that Apple suggests using exceptions for truly exceptional conditions, and NSError for expected failures. Since you come from Java, I think NSError -> java.lang.Exception, and Obj-C Exceptions -> java.lang.RuntimeException. Use an Obj-C exception when the programmer did something wrong (used an API incorrectly, for example), and use NSError when an expected failure occurred (the remote server could not be found, for example).

Of course, that's just my interpretation of Apple's position. I, on the other hand, like exceptions!

Daniel Yankowsky
  • 6,956
  • 1
  • 35
  • 39
  • 2
    You've captured Apple's position correctly. Errors are foreseeable problems you'll probably want to present to the user. Exceptions should either never happen or always be caught. Cocoa is designed around this, as exemplified by AppKit's new error-presentation system. http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/ErrorHandlingCocoa/ErrorHandling/ErrorHandling.html http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/Exceptions/Exceptions.html – Peter Hosey Jan 05 '10 at 16:40
  • Alright, I think I understand. It seems like Apple is giving us two ways of communicating errors up the function call stack, and that out of the two ways NSError is the one better supported by Cocoa. I'd like to go with the method that is better supported.. but the control flow features of exceptions seem to make for less redundant code. Any chance there's something in Obj-C/Cocoa that addresses this? –  Jan 05 '10 at 17:27
  • JC Reus: No, you have that wrong. Errors and exceptions are both supported, but they are for different purposes. – Peter Hosey Jan 07 '10 at 07:38
  • JC Reus: I found this part of your sentence illuminating: “…two ways of communicating errors…” No. In Cocoa, **errors and exceptions are different things**. Cocoa provides a way of presenting errors to the user because that's what they're for: Things that the user should know and may need to handle (although you may handle the error yourself if you can). It provides no such mechanism for exceptions because handling exceptions is exclusively *your* job. They do not compete with each other for the same task; they are for completely separate tasks. – Peter Hosey Jan 07 '10 at 14:04
3

Exceptions in Objective-C have historically been 'heavy', with a performance cost to entering a try block, a cost to throwing, a cost to using finally, etc. As a result Cocoa developers have typically avoided exceptions outside of 'oh no, the sky is falling' sorts of situations -- if a file is missing, use an NSError, but if there's no filesystem and a negative amount of free memory, that's an exception.

That's the historical view. But if you're building a 64-bit app on 10.5 or newer, the exception architecture has been rewritten to be 'zero cost', which may mean that the historical view is no longer relevant. As with just about anything, it comes down to various factors -- if working one way is more natural to you and will let you finish more quickly, and if you don't experience any performance-related problems with it, and if being slightly inconsistent with 'traditional' Objective-C code doesn't bother you... then there's no reason not to use exceptions.

Skirwan
  • 1,362
  • 1
  • 10
  • 11
  • Just because the exception mechanism in Obj-C for the 64-bit runtime is low cost to enter doesn't mean that it is safe to use for Cocoa. If you're writing your own apps and your own frameworks, it's no problem, but AppKit, UIKit and Foundation are NOT exception safe, meaning you can't throw exceptions across them and catch them later safely, like in Java. – Jason Coco Jan 05 '10 at 21:03
  • There is absolutely every reason to *not* use exceptions. Namely, that the system frameworks are *not* exception safe. Thus, even if you do all of your exception throws and catches in isolation from the system frameworks, it'll be unnecessarily different & you are going to have hell to pay if you want to refactor later to use a system API somewhere. – bbum Jan 06 '10 at 05:13
  • Can you give me an example of how you'd have hell to pay? Unless you're in a situation where you're trying to throw exceptions *through* a system API, I don't see how using exceptions in your own code is going to have any negative interactions. – Skirwan Jan 06 '10 at 20:27
  • It is trivially easy to wind up throwing an exception through a system API. For example, if you throw an exception from within a delegate callback. – Chris Hanson Jan 07 '10 at 00:28
  • Generally when I'd want to throw an exception it would be in the case where I planned to catch it as well. Wouldn't this make it much less likely to get thrown *through* a system API? The question seems to me to be a matter of convenience: a single catch block with exceptions can be much less of a pain to code, read, refactor and maintain than dozens of conditionals and redundant actions. Isn't this why exceptions were invented in the first place? – Hari Honor Feb 02 '12 at 10:13
  • And the whole purpose of an exception is to create a second, exceptional, code path that jumps over whatever frames exist between the throw and the catch. So while the code may be readable, the actual behavior is extremely hard to implement correctly in modular system with lots of intricate code reuse. Thus, the decision was made *not to use exceptions for recoverable errors* in Cocoa/iOS. And, thus, the frameworks don't support it. Which means you shouldn't do it either because it'll just make your code harder to maintain. – bbum Sep 20 '12 at 16:01
2

According to More iPhone 3 Development by Dave Mark and Jeff LeMarche, exceptions in are used only for truly exceptional situations and usually indicate a problem within your code. You should never use exceptions to report a run-of-the-mill error condition. Exceptions are used with much less frequency in Objective-C than in many other languages, such as Java and C++.

You use an exception when you need to catch a mistake in your code. You use an error when the user may need to fix the problem.

Here's an example where you would use an exception:

We're writing a superclass, and we want to make sure its subclasses implement a given method. Objective-C doesn't have abstract classes, and it lacks a mechanism to force a subclass to implement a given method. Yet we can use an exception to instantly inform us that we forgot to implement the method in a subclass. Instead of an unpredictable behavior, we'll get slammed with a runtime exception. We can easily debug it because our exception will tell us exactly what we did wrong:

NSException *ex = [NSException exceptionWithName:@"Abstract Method Not Overridden" reason:NSLocalizedString(@"You MUST override the save method", @"You MUST override the save method") userInfo:nil];
[ex raise];

Because problem is a programmer mistake rather than a problem the user may be able to fix, we use an exception.

Rose Perrone
  • 61,572
  • 58
  • 208
  • 243
  • If that were the only case to ever use an exception, then what point would there be to even having exceptions? Wouldn't you just print the error and exit() - like in the ol' C days before exceptions existed? – Hari Honor Feb 02 '12 at 10:25
  • Thanks for asking this question. Benefits to using exceptions are described here: https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Exceptions/Tasks/ControllingAppResponse.html. You can use NSExceptions to "print a descriptive log and a stack trace to the console when such an error (unchecked NSException) occurs" and "handle the error in such a way as to prevent the resulting abrupt termination." – Rose Perrone Feb 04 '12 at 01:00
  • Yes. But you generally wouldnt want to handle a programming mistake "in such a way as to prevent...abrupt termination" – Hari Honor Feb 04 '12 at 11:15
  • @HariKaramSingh You can still terminate, just not 'abruptly'. Presenting a nice error message dialog etc. Xcode (the app itself) even goes as far as giving an option of ignoring the exception and continuing execution, which I always take, and would be much more irritated if Xcode crashed every time instead. – Andrey Tarantsov Jul 11 '13 at 13:53