57

I have just completed an iPhone app programming course. As part of the course, I saw

  • Objective-C provides exception handling using the @try directive
  • The system library does not use exception handling, preferring to return nil

I asked if I should use exception handling for new code I wrote (e.g. if I write both the front-end and logic code, so the communication between them is in my hands) but I was told no, one should not use exceptions for new code. (But he failed to elaborate, then the class moved on, I thought perhaps the reason would become clear later..)

Surely exceptions are superior to return nil? You can catch particular types, you are not tempted to ignore them by ignoring the return type of a function which normally succeeds, you have text messages which can be logged, they allow your code to focus on the normal case and thus be more readable. Why to use exceptions.

So what do you think? Was my trainer right not to use Objective-C exceptions? If so, why?

Adrian Smith
  • 17,236
  • 11
  • 71
  • 93

7 Answers7

70

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, NSRunLoops 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.

zneak
  • 134,922
  • 42
  • 253
  • 328
  • 2
    What about releasing the objects @finally blocks? (Or is that too inelegant and results in too much code?) – Adrian Smith Jan 10 '11 at 16:58
  • 3
    @Adrian Smith Releasing in a `@finally` is fine in your own methods. However, methods that call yours may or may not have `@finally` blocks to clean up if an exception from your method propagates to them. – zneak Jan 10 '11 at 17:01
  • You have to count on all frames of framework code also correctly using `@finally` or other mechanisms to also release all objects. It is too tedious and too much code, yes. It also incurs a non insignificant amount of overhead. – bbum Jan 10 '11 at 17:02
  • 3
    Actually the problem doesn't disappear with GC. There are resources other than memory that need to be released. – JeremyP Jan 10 '11 at 17:03
  • @bbum I agree with you, though I'm pretty sure `@finally` blocks incur little to no speed overhead. – zneak Jan 10 '11 at 17:04
  • 1
    @bbum Last time I checked assembly from clang (which was about a year ago), the contents of `@finally` blocks was just appended to every code path that could need it. – zneak Jan 10 '11 at 17:43
  • What happens during the unwind; does the compiler generate a jmp through every @finally? ... does it generate a jump over all non-@finally'd frames? (Been a while since I looked). Also -- byte size of the @finally's as % of frame is interesting, too. I should look. – bbum Jan 10 '11 at 17:45
  • @bbum I don't remember and I can't test right now. I'll look into it at home. – zneak Jan 10 '11 at 17:56
  • One detail is missing - I'm not throwing, but should I catch? some Cocoa frameworks (even Core frameworks) throw! my code then unwinds, and since I have several parallel dispatch/operation queues, app continues to work normally - and again I have leaks! is it OK to @catch without ever throwing? – Motti Shneor Jan 20 '21 at 08:47
36

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.

Iulian Onofrei
  • 9,188
  • 10
  • 67
  • 113
bbum
  • 162,346
  • 23
  • 271
  • 359
  • 1
    Okay, so in Objective-C we don't use exceptions for regular conditions. Would it be out of place to ask if they can be used for abnormal conditions (unexpected situations, etc.) without much cost, or is there a ton of overhead (speed/memory)? Of course I'm asking about places where there is a LOW probability of an exception being thrown. – Dan Rosenstark Nov 01 '14 at 03:00
  • 1
    @Yar The cost of throwing an exception is effectively nothing because you should only throw exceptions when the program has gone off the rails to the point of not being recoverable. So, no, in general, no need to worry about overhead because the app is about to crash anyway. – bbum Nov 01 '14 at 07:30
  • Thank you. So the cost of the @catch block itself **if no excpetion is thrown** is near-nil, correct? – Dan Rosenstark Nov 03 '14 at 16:38
  • 1
    Right-- it should have minimal impact. – bbum Nov 04 '14 at 01:36
8

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

Abizern
  • 146,289
  • 39
  • 203
  • 257
  • I totally agree with you, and even if Exception handling can be applied --- it is a poor decision in the context of Objective-C and Cocoa - because of their dynamic nature. – Motti Shneor Jan 20 '21 at 09:00
3

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.

JeremyP
  • 84,577
  • 15
  • 123
  • 161
  • The problem, though, with this approach is refactoring. As soon as your refactor the code such that an exception is thrown through framework code, you also have to refactor the exception handling to catch the exception before returning to the framework and then re-throwing it on the caller side. It proves to be a *huge* maintenance headache over time. – bbum Jan 10 '11 at 17:25
  • @bbum: But you wouldn't refactor the code such that the exception is thrown though framework code since that would be an eternal interface. – JeremyP Jan 11 '11 at 08:30
  • Not sure what your comment means; the issue with refactoring is that if you change your code to put your exception-heavy classes into, say, an Apple provided collection class, you have to refactor along the boundary to prevent an exception every being passed out of something like -performSelector: or -enumerateWithBlock: – bbum Jan 11 '11 at 22:58
  • @bbum: That is true. But any selector that is part of the public API shouldn't raise exceptions (unless explicitly documented), and if somebody else is using your private API, they deserve what they get. – JeremyP Jan 12 '11 at 10:34
  • I do not agree. Exceptions not only aren't "cleaner" they are clear dead-end when applied to multi-level code and 3rd party code. Their runtime implications (when stack unrolling) in a dynamic language like Obj-C are drastically hitting performance. With retain-counts and method-swizzling and 'id' generic programming, it is almost impossible to apply exception handling reasonably to Cocoa applications – Motti Shneor Jan 20 '21 at 08:58
2

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.

Evan Mulawski
  • 54,662
  • 15
  • 117
  • 144
  • That is *not* a common use for `@try/@catch` blocks. In fact, the behavior of that line of code is undefined. Exceptions are used to indicate unrecoverable programmer error and should not be used to attempt recovery from erroneous state or for flow control. – bbum Jan 10 '11 at 16:46
  • @bbum: Do you have source for that, or is that your personal opinion? Also, according to the docs, it will raise an exception: http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSMutableDictionary_Class/Reference/Reference.html – Evan Mulawski Jan 10 '11 at 16:48
  • It is well documented; https://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/ObjectiveC/Articles/ocExceptionHandling.html%23//apple_ref/doc/uid/TP30001163-CH13 That it throws an exception does not mean the exception is meant to be caught and recovered from. An Exception in iOS/Cocoa means that a non recoverable programmer error has been detected; it is an indication of a bug in your code. – bbum Jan 10 '11 at 16:54
  • Think about what you are saying. *ALL* exceptions are meant to be caught and analyzed to determine *if* they can be recovered from. Otherwise, why would the OS throw them in the first place? In addition, exceptions that have been thrown don't always indicate a bug in your code; they can be the result of internal framework inconsistencies or libraries that you don't have control over. – Evan Mulawski Jan 10 '11 at 16:57
  • Exceptions in Cocoa/iOS are **not** meant to be recovered from (save for, like, 2 or 3 methods that slipped through the cracks -- NSDictionary's `setObject:forKey:` is *not* one of those). They indicate programmer error. That they are thrown in the first place is to give an app a chance to "fail gracefully" as opposed to simply disappear. – bbum Jan 10 '11 at 17:01
  • Also, in the link in your comment: **In many environments, use of exceptions is fairly commonplace. For example, you might throw an exception to signal that a routine could not execute normally—such as when a file is missing or data could not be parsed correctly.** – Evan Mulawski Jan 10 '11 at 17:01
  • Yes -- and Cocoa is not one of those environments. See the documentation I excerpted on my answer. (Note that I have yet to express my opinion about exceptions -- what I have stated is fact. I also completely understand the confusion; I did Java for ~10 years and know well how seemingly natural & integral exceptions can feel in an OO environment). – bbum Jan 10 '11 at 17:04
1

I think your trainer is correct. You can make all kinds of arguments for and against exceptions but the bottom line is if you want your code to "smell" right to an experienced Cocoa developer, you'll implement your application using the same design patterns that Apple use in their code and in their frameworks. That means nils and NSErrors rather than exceptions.

The advantages of not using exceptions are therefore mainly around consistency and familiarity.

An other thing to consider is that a nil in Objective C is usually fairly "safe." That is, you can send any message to a nil and your application won't crash.

(I should also point out that Apple do use exceptions in some places, usually where you're using an API incorrectly.)

Stephen Darlington
  • 51,577
  • 12
  • 107
  • 152
0

if you prefer exceptions, then you can use them. if you do, i recommend you use them sparingly because it becomes very hard to maintain (additional exit points which you'll typically have to exercise at runtime to prove the program's correctness).

there's not a specification for exception handling expectations for clients; they have to maintain their program based on the docs (tedious, error prone, probably won't be maintained until an exception is thrown).

if i were to use them, i'd use them only in very rare cases and use error codes or return nil where possible. plus, i'd use them internal to a library and not expose the clients to exceptions in the interface (or the side effects). i don't use them in objc or c++ (well, i will catch them but i won't throw them).

as you'd usually expect, you should avoid using them to control program flow (a common misuse).

it's better to use them when you have to escape a certain crash. if you're going forward and writing the code now, i recommend you just write the interface to handle the errors as part of the program flow and avoid writing exceptions where possible.

correction to original post: cocoa libs prefer to return nil, in some cases they will throw exceptions for you (to catch).

justin
  • 104,054
  • 14
  • 179
  • 226
  • You cannot use exceptions in Cocoa / iOS programming *unless* you ensure that you throw the exceptions across set of stack frames that are comprised *only of code under your control*. Through an exception through the frameworks is undefined behavior. – bbum Jan 10 '11 at 17:07
  • @bbum i'm not sure if that was a criticism of my response, or just an additional fact. fwiw, i recommended insulating clients from use of exceptions in the third paragraph, while discouraging their use in general. – justin Jan 10 '11 at 17:23
  • Just stating a fact for clarification. – bbum Jan 10 '11 at 17:24