Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does NSOperation example code uses @try & @catch

In Apple's Concurrency Programming Guide the NSOperation subclass examples (both non-concurrent and concurrent varieties) use exception handling and I'm wondering why they are encouraging this style within operations.

Listing 2-4 Responding to a cancellation request

- (void)main {
   @try {
      BOOL isDone = NO;

      while (![self isCancelled] && !isDone) {
          // Do some work and set isDone to YES when finished
      }
   }
   @catch(...) {
      // Do not rethrow exceptions.
   }
}

My understanding is that generally exception handling is not a common practice in Objective-C code - exceptions are essentially programmer errors and should cause the app to crash whereas unexpected inputs are best handled by NSError. (My possibly misinformed understanding comes from things like this and this)

I'm wondering if NSOperations present a particular situation in which exception handling is important, or if this is more the preferred style of the particular author of that guide.

As a side note, some of the NSOperation example code follows this style, other examples do not. Most high-visibility OSS does not use exceptions (AFNetworking, for example).

like image 248
edelaney05 Avatar asked Dec 02 '12 16:12

edelaney05


2 Answers

Your understanding is correct - NSError (or similar) should be used to convey error information, rather than exceptions. Most Objective-C code is not exception-safe and will at the very least leak resources. As a general rule, never let your code leak an exception into anyone else's code - whether Apple's or a 3rd parties. Some 3rd party frameworks may explicitly indicate they are exception safe, but it's rare.

By that principle you can see why you should have a catch-all exception handler in your main method regardless. But there's actually another reason: your operation will be run on a dedicated thread. Exceptions thrown from your operation will propagate up the stack, but no further. The logical caller or owner of the operation won't get them, as they're running on a different thread (or not at all). So leaked exceptions will either kill your whole program, or be swallowed silently with no other indication. Your program may then get stuck in a weird state - since you didn't realise an error occurred, you may continue waiting for the result of your operation that will never arrive.

Additionally, Apple has a section in the Concurrency Programming Guide where they talk about Handling Errors and Exceptions. Their first point on "discrete entities" is alluding to what I said in the previous paragraph:

Handling Errors and Exceptions

Because operations are essentially discrete entities inside your application, they are responsible for handling any errors or exceptions that arise. In OS X v10.6 and later, the default start method provided by the NSOperation class does not catch exceptions. (In OS X v10.5, the start method does catch and suppress exceptions.) Your own code should always catch and suppress exceptions directly. It should also check error codes and notify the appropriate parts of your application as needed. And if you replace the start method, you must similarly catch any exceptions in your custom implementation to prevent them from leaving the scope of the underlying thread.

Among the types of error situations you should be prepared to handle are the following:

  • Check and handle UNIX errno-style error codes.
  • Check explicit error codes returned by methods and functions.
  • Catch exceptions thrown by your own code or by other system frameworks.
  • Catch exceptions thrown by the NSOperation class itself, which throws exceptions in the following situations:
    • When the operation is not ready to execute but its start method is called
    • When the operation is executing or finished (possibly because it was canceled) and its start method is called again
    • When you try to add a completion block to an operation that is already executing or finished
    • When you try to retrieve the result of an NSInvocationOperation object that was canceled

If your custom code does encounter an exception or error, you should take whatever steps are needed to propagate that error to the rest of your application. The NSOperation class does not provide explicit methods for passing along error result codes or exceptions to other parts of your application. Therefore, if such information is important to your application, you must provide the necessary code.

like image 186
Wade Tregaskis Avatar answered Nov 14 '22 23:11

Wade Tregaskis


I think this post and the accompanying answer elaborates very well on the general exception- vs. no exception handling topic!

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.

like image 3
Nenad M Avatar answered Nov 14 '22 23:11

Nenad M