3

From other questions, I can see that it is recommended that NSError be used for recoverable errors, and @throw/@catch/@finally and NSException should be used for fatal errors.

This makes no sense to me. Why would you use NSException for fatal errors? The whole point is that they can be caught! If catching them isn't the point, why have the @try/@catch system at all? Why not just NSLog, exit(1), and call it a day?

NSError is also really clunky, which drives me to prefer @throw/@catch/@finally

What motivates the use of one over the other?

Alexander
  • 59,041
  • 12
  • 98
  • 151
  • What you do _internally_ is your affair. But NSException is not so easily caught. And suppose your code is being used from Swift. The Swift programmer can catch your NSError but not your NSException. – matt Feb 01 '18 at 03:06
  • @matt "not so easily caught." Why's that? And yea, I'm aware that Swift doesn't support `NSException`, which also makes no sense to me. My best guess is that NSError is already the dominant error-handling mechanism, so there was no need to implement support for catching NSException in swift. But that doesn't explain why NSError was dominant, pre-Swift. – Alexander Feb 01 '18 at 03:12
  • Suppose Cocoa throws an NSException. What usually happens is not that you catch it; what usually happens is that you crash. But when Cocoa hands you an NSError, it is sending you a message explaining why it couldn't return the requested value. Those are the Cocoa design patterns. – matt Feb 01 '18 at 03:14
  • So NSError was dominant because it was more easily ignored? – Alexander Feb 01 '18 at 03:16
  • 1
    @Alexander The fact that it can be ignored is a byproduct of the rules of the language, not the conventions of the framework (e.g. if Obj-C had error rules like Swift does, you wouldn't be able to ignore them). See my comments on Matt's answer too — the key difference here is that one is meant for _programmer_ error, and the other is for "expected" errors in during normal runtime. – Itai Ferber Feb 01 '18 at 03:41
  • The indirect return of `NSError` morphed _directly into_ the Swift error system, except Swift picked the (incredibly ugly) try/catch syntax to represent it. They're equivalent in effect. – jscs Feb 01 '18 at 13:40

2 Answers2

2

Those are the long-standing Cocoa design patterns.

  • An NSError object is returned in good order (usually by indirection) from your request for a value (e.g. "please try to make a URL from this string"). It is effectively substituted when the value could not be supplied, and is in effect an elaborate message, a packet of information that might include text you can show the user, suggestions for how the user might recover from the situation, and so forth.

  • An NSException happens because the programmer has made a serious mistake. It represents a fatal problem, so it percolates up the chain and brings the program to an abrupt end. The message it contains is meant only for the programmer.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • 2
    This is exactly the gist of it. `NSException` is meant to signal _programmer_ error; they _can_ be caught, but are not meant to. Not every part of the Cocoa frameworks supports recovering from a thrown exception, and many are assumed to end program execution. Without a [special flag](https://clang.llvm.org/docs/AutomaticReferenceCounting.html#exceptions), too, ARC will not attempt to reorder `retain`s and `release`s near exception-handling code to make sure that you don't leak objects if an exception is actually caught (since the assumption is that the process is going to go down soon anyway). – Itai Ferber Feb 01 '18 at 03:37
  • 1
    All recoverable errors, then, go through `NSError`, whose MO is end-user presentability. The error might be serious, but if it's not due to programmer error, it should be handled and presented to the user in some fashion (which is why `NSError`s are localized to such an extent). – Itai Ferber Feb 01 '18 at 03:39
  • @ItaiFerber "they can be caught, but are not meant to." That sounds crazy to me. Why have `@catch` at all then? Why bother with `@throw`? Why not just `NSLog` + `exit(1)` – Alexander Feb 01 '18 at 05:08
  • @matt I don't understand your first point. `NSException` has [`reason`](https://developer.apple.com/documentation/foundation/nsexception/1415537-reason). It doesn't have the same localization support, but it could be made to if there was interest for it. – Alexander Feb 01 '18 at 05:10
  • @matt Your answer is factually true, but it's an observation of the state of an affairs, but not an explanation as to why we got to this state of affairs. Why not use `NSException.reason` over `NSError`? Why couldn't both programmar and non-programmer errors be unified under `NSException`, with different names/reasons? – Alexander Feb 01 '18 at 05:11
  • @matt The object leaking and non-localizability that Itai alluded to are also observations of the state of affairs, but the motivations remain unknown. Why isn't `-fobjc-arc-exceptions` enabled by default for ObjC (it is for ObjC++)? Why isn't the `NSException` made to be localized? – Alexander Feb 01 '18 at 05:14
  • I hope my question isn't opinion based or too broad, because I suspect there's a very compelling technical explanation for why `NSError` has been preferred, and continued being preferred even after the introduction of `NSException`. Perhaps it was just the momentum behind `NSError` and the desire to not break all the existing APIs – Alexander Feb 01 '18 at 05:18
  • I saw this on the docs for `-fobjc-arc-exceptions`: "exceptions-safe by default would impose severe runtime and code size penalties on code that typically does not actually care about exceptions safety", but that still rests upon an assumption that Cocoa should prefer NSError over NSException, without explaining why. – Alexander Feb 01 '18 at 05:24
  • 1
    @Alexander @catch wasn’t always built in to the language, and IIRC, before `NSException` switched to using C++ exception mechanisms, they were most costly to catch and throw than they are today. But besides ergonomics, I don’t think there’s a really deep technical reason for this — it’s mostly a philosophical choice. Programmer errors are relatively rare, but runtime errors are common, and should always be user-presentable. Errors are cheap to return by reference, especially compared to exceptions (and used to be more so); everything else built up around the conventions. – Itai Ferber Feb 01 '18 at 06:05
  • I can try to poke around for some old design documents about this on my team, but I don’t think anything will come up that I didn’t cover here. It’s not that `NSException` _can’t_ do the things that `NSError` does, it’s just that it never did. API, especially old APIs like this, have a lot of momentum. They developed into what they are now long enough ago that all of the tooling and compiler support built up around the conventions and not the other way around. – Itai Ferber Feb 01 '18 at 06:07
  • 1
    It’s hard to find good sources on this at the moment, but I believe `@try` and `@catch` were introduced with ObjC 2.0, and at that point, the `NSException` infrastructure switched over to using C++ exception mechanisms. Up to that point I think `NSException` was built on top of `setjmp`/`longjmp`. This may or may not be the technical reasoning you’re looking for... – Itai Ferber Feb 01 '18 at 06:17
  • 1
    Btw, there are some exceptions(!) to the "usual pattern". For example `+[NSExpression expressionWithFormat:]` throws an exception if the format string is ill-formed (which cannot be catched in Swift, and makes NSExpression practically useless for user provided input). – Martin R Feb 01 '18 at 07:54
  • More history: bbum has a comment or an answer about this somewhere on Stack Overflow, but ObjC exception throws were at one point just `setjmp`/`longjmp` pairs. You end up with orphaned stack frames, with no opportunity to run clean up. In particular, Cocoa itself is potentially in a bad state. Given that, your only option was to bail. – jscs Feb 01 '18 at 13:47
  • @ItaiFerber "Programmer errors are relatively rare, but runtime errors are common, and should always be user-presentable. Errors are cheap to return by reference, especially compared to exceptions" I think that's a decently satisfying answer, you should write is up as an answer. – Alexander Feb 01 '18 at 16:31
  • @JoshCaswell That makes sense, and I guess the momentum behind the existing Cocoa framework blocked the possibility of migrating NSError apis to throwing, once that became fast enough? – Alexander Feb 01 '18 at 16:32
  • Yup, changing the framework itself plus all existing third party code...too much. That's my assumption, @Alexander. Plus, I suspect not everyone (_raises hand_) shares your dislike of the NSError scheme. :) – jscs Feb 01 '18 at 17:18
  • @JoshCaswell Not everyone (raises hand) shares _your_ dislike of Swift's `try/catch`. The indirect return of NSError was clunky and (worse) required the programmer to understand how to use the pattern correctly — most of them, judging by Stack Overflow, didn't — but it was the best Objective-C could do, because it couldn't return a tuple which would have been ideal in many cases. Given the pattern, I think the way Swift swallows the `NSError**` parameter and folds it implicitly into the `try/catch` structure is brilliant, because _that_ is something programmers will get right. – matt Feb 01 '18 at 18:04
  • Heh, fair. I agree it's an largely an improvement; and in fact bifurcating the return value is clever in a good way. My only real objection is the segregation and indentation of the conditional expression that's controlling execution flow inside a `do` block. – jscs Feb 01 '18 at 18:24
  • "My only real objection is the segregation and indentation of the conditional expression that's controlling execution flow inside a do block" I agree, that's a bit maddening, esp. when you need to declare local variables to be visible outside the block. OTOH that's why God gave us `try?`. – matt Feb 01 '18 at 18:31
  • Yep, I like `try?`, but then of course you lose the error information...I've been moving towards just using `Result`s. – jscs Feb 03 '18 at 15:38
-1

Apple said:

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.

 

Instead of exceptions, error objects (NSError) and the Cocoa error-delivery mechanism are the recommended way to communicate expected errors in Cocoa applications.

Source: Exception Programming Topics

Willeke
  • 14,578
  • 4
  • 19
  • 47
  • See the last line of my question – Alexander Feb 01 '18 at 16:27
  • @Alexander Apple is not going to tell why, maybe something in the bowels of the frameworks. I know from experience: if you don't play by Apple's rules, you'll get into trouble. This is why other questions recommend `NSError`. – Willeke Feb 04 '18 at 09:55
  • Nowadays Apple says "Use Swift" and "Error handling is the process of responding to and recovering from error conditions in your program. Swift provides first-class support for throwing, catching, propagating, and manipulating recoverable errors at runtime.". Maybe in the future the frameworks will be in Swift and `NSError` will disappear. – Willeke Feb 04 '18 at 09:59