Objective-C Exceptions
Solution 1:
It is unsafe to throw exceptions in circumstances where resources are not automatically managed. This is the case of the Cocoa framework (and neighbor frameworks), as they use manual reference counting.
If you throw an exception, any release
call you skip over by unwinding the stack will result in a leak. This should limit you tothrowing only if you're certain that you're not going to recover since all resources are returned to the OS when a process quits.
Unfortunately, NSRunLoop
s tend to catch all exceptions that propagate to them, so if you throw during an event, you'll resume to the next event. This is, obviously, very bad. Therefore, it's better that you simply don't throw.
This problem is diminished if you use garbage-collected Objective-C, as any resource represented by an Objective-C object will be properly released. However, C resources (such as file descriptors or malloc
-allocated memory) that are not wrapped in an Objective-C object will still leak.
So, all in all, don't throw.
The Cocoa API has several workarounds to this, as you mentioned. Returning nil
and the NSError**
pattern are two of them.
Clarifications for ARC
ARC users can choose to enable or disable full exception safety. When exception safety is enabled, ARC will generate code to release strong references when their scope is killed, making it safe to use exception in your code. ARC will not patch external libraries to enable exception support in them, so you should be careful where you throw (and especially where you catch), even with exception support enabled in your program.
ARC exception support can be enabled with -fobjc-arc-exceptions
or disabled with -fno-objc-arc-exceptions
. By default, it is disabled in Objective-C but enabled in Objective-C++.
Full exception safety in Objective-C is disabled by default because the Clang authors assume that Objective-C programs will not recover from an exception anyways, and because there is a large code size cost and a small performance penalty associated to that cleanup. In Objective-C++, on the other hand, C++ already introduces a lot of cleanup code, and people are much more likely to actually need exception-safety.
This is all from the ARC specification on the LLVM website.
Solution 2:
In Cocoa and iOS programming, exceptions are used to indicate non-recoverable programmer error. When an exception is thrown by the frameworks, it indicates that the frameworks have detected an error state that is both not recoverable and for which the internal state is now undefined.
As well, exceptions thrown through framework code leave the frameworks in an undefined state. I.e. you can't do something like:
void a() {
@throw [MyException exceptionWithName: @"OhNoes!" ....];
}
@try {
... call into framework code that calls a() above ...
} @catch (MyException e) {
... handle e ...
}
Bottom line:
Exceptions are not to be used in Cocoa for flow control, user input validation, data validity detection or to otherwise indicate recoverable errors. For that, you use the NSError**
pattern as documented here (thanks Abizem).
(Note that there is a small number of API that does violate this -- where an exception is used to indicate a recoverable state. Bugs have been filed against these to deprecate and eventually remove these inconsistencies.)
Finally found the document I was looking for:
Important: You should reserve the use of exceptions for programming or unexpected runtime errors such as out-of-bounds collection access, attempts to mutate immutable objects, sending an invalid message, and losing the connection to the window server. You usually take care of these sorts of errors with exceptions when an application is being created rather than at runtime.
If you have an existing body of code (such as third-party library) that uses exceptions to handle error conditions, you may use the code as-is in your Cocoa application. But you should ensure that any expected runtime exceptions do not escape from these subsystems and end up in the caller’s code. For example, a parsing library might use exceptions internally to indicate problems and enable a quick exit from a parsing state that could be deeply recursive; however, you should take care to catch such exceptions at the top level of the library and translate them into an appropriate return code or state.
Solution 3:
I think, and others will correct me if I am wrong, that Exceptions should be used to catch programmer errors, while the NSError
type error handling should be used for exceptional conditions that occur while the program is running.
And as for returning nil
, that isn't all - functions that might have problems don't just return a nil
, they also can (and should) provide further information using the NSError object that is passed in as a parameter.
See also
- Error Handling Programming Guide
- A useful blog post from Cocoa Is My Girlfriend.
Solution 4:
Personally, I see no reason not to use exceptions in your own code. The exception pattern is cleaner for handling errors than
- always having a return value,
- making sure that one of the possible return values really means "error"
- passing an extra parameter which is a reference to an
NSError*
- sprinkling your code with clean up code for every error return or having explicit gotos to jump to a common error cleanup section.
However, you need to bear in mind that other people's code is not guaranteed to handle exception properly (if it's pure C, in fact it can't handle exceptions properly). Thus, you must never ever allow an exception to propagate beyond the boundaries of your code. For instance, if you throw an exception deep in the bowels of a delegate method, you must handle that exception before returning to the sender of the delegate message.
Solution 5:
The type of error handling used depends on what you are trying to accomplish. Almost all of Apple's methods will populate a NSError
object that can be accessed upon error.
This type of error handling can be used in conjunction with a try-catch block within a section of code:
-(NSDictionary *)getDictionaryWithID:(NSInteger)dictionaryID error:(NSError **)error
{
@try
{
//attempt to retrieve the dictionary
}
@catch (NSException *e)
{
//something went wrong
//populate the NSError object so it can be accessed
}
}
In a nutshell, the purpose of the method determines the type of error handling you should use. But, in this example above, you can use both.
A common use for the try-catch block:
@try
{
[dictionary setObject:aNullObject forKey:@"Key"];
}
@catch (NSException *e)
{
//one of the objects/keys was NULL
//this could indicate that there was a problem with the data source
}
Hope this helps.