20

How to handle errors for methods or code that does not explicitly throw?

Wrapping it a do / catch block results in a compiler warning:

"'catch' block is unreachable because no errors are thrown in 'do' block"

Coming from C# / JAVA background this is an oddity to say the least. As a developer I should be able to safeguard and wrap any code block in do/catch block. Just because a method is not explicitly marked with "throw" does not mean that errors will not occur.

rmaddy
  • 298,130
  • 40
  • 468
  • 517
AlexVPerl
  • 6,653
  • 5
  • 36
  • 74
  • 7
    If there is no explicit `throw` then there is nothing to catch. What's the point of `do/catch` if there's nothing being thrown? – rmaddy Feb 02 '17 at 05:09
  • 5
    Well, just because a method does not explicitly "throw" does not mean that a run-time error will not occur :) – AlexVPerl Feb 02 '17 at 05:16
  • 1
    It means there is no catchable error. You can't catch uncatchable runtime errors. – rmaddy Feb 02 '17 at 05:19
  • In other languages you can catch any error regardless whether the method is declared with "throw". There has to be a way of handling this scenario. – AlexVPerl Feb 02 '17 at 05:23
  • No, Swift is different. Uncatchable errors are just that - uncatchable. It's been a long time since I coded in Java but isn't there the same in Java - FatalException? – rmaddy Feb 02 '17 at 05:24
  • Wow, seems like a terrible decision made by language designers. Java allows you to wrap any code in try catch. – AlexVPerl Feb 02 '17 at 05:46
  • 1
    Java has uncatchable exceptions (errors). Swift is no different. – rmaddy Feb 02 '17 at 05:48
  • 1
    Thanks, but to be honest I never heard of it. In java if the code is wrapped in try/catch and catch does not have a filter - the exception will be caught regardless. – AlexVPerl Feb 02 '17 at 05:51
  • 9
    @AlexVPerl It's not a bad design decision – fatal errors at runtime *shouldn't* be caught, as they indicate a programmer error (such as accessing an index out of bounds for a collection or force unwrapping nil). You shouldn't catch such errors, you should fix your code so that they don't occur in the first place. – Hamish Feb 02 '17 at 11:57
  • 1
    If you are working with a mixed ObjectiveC and Swift codebase, there are may ways in which an exception may be thrown at runtime that is not indicated in the method signature. Adding guard code like this can be really helpful for crashes that are indicated at runtime by users but you cannot reproduce, so you can add additional logging information to find the root cause. Currently adding this guard code gives you a compiler warning which it would be preferable to disable so as not to pollute the build log. – Kendall Helmstetter Gelner Jul 17 '17 at 21:45
  • 1
    @KendallHelmstetterGelner: Objective-C exceptions (NSException) *cannot* be catched in Swift, only in Objective-C, see e.g. https://stackoverflow.com/questions/32758811/catching-nsexception-in-swift. – Martin R Jul 18 '17 at 11:52
  • @MartinR - Thanks for the note, I somehow missed since Swift2 you could not catch ObjC exceptions in Swift! Out of fairness though I'll leave the bounty up for anyone who can figure out how to disable the warning. – Kendall Helmstetter Gelner Jul 18 '17 at 22:29
  • `do/try/catch` is a nice handholding feature that *some* programming languages provide - not every language provides this kind of feature, and those that do (like Swift and Java) don't necessarily evaluate and catch *every kind of issue* within the `do/try/catch` block – Jacob Barnard Jul 21 '17 at 13:41

6 Answers6

7

Faced with an exception thrown from a method which cannot throw. Found out this exception was thrown from objective-c part of API. So you should catch it in old style way using objective-c.

Firstly create objective-c class which takes several blocks in init method - for try, catch and finally.

#import <Foundation/Foundation.h>

/**
 Simple class for catching Objective-c-style exceptions
 */
@interface ObjcTry : NSObject

/**
 *  Initializeer
 *
 *  @param tryBlock
 *  @param catchBlock
 *  @param finallyBlock
 *
 *  @return object
 */
- (_Nonnull id)initWithTry:(nonnull void(^)(void))tryBlock catch:(nonnull void(^)( NSException * _Nonnull exception))catchBlock finally:(nullable void(^)(void))finallyBlock;

@end

In .m file:

#import "ObjcTry.h"

@implementation ObjcTry

- (_Nonnull id)initWithTry:(nonnull void(^)(void))tryBlock catch:(nonnull void(^)( NSException * _Nonnull exception))catchBlock finally:(nullable void(^)(void))finallyBlock
{
    self = [super init];
    if (self) {
        @try {
            tryBlock ? tryBlock() : nil;
        }
        @catch (NSException *exception) {
            catchBlock ? catchBlock(exception) : nil;
        }
        @finally {
            finallyBlock ? finallyBlock() : nil;
        }
    }
    return self;
}

@end

Second, add its headers to the Bridging Header file.

#import "ObjcTry.h"

And use it in your swift code like that:

var list: [MyModel]!
_ = ObjcTry(withTry: {
    // this method throws but not marked so, you cannot even catch this kind of exception using swift method.
    if let items = NSKeyedUnarchiver.unarchiveObject(with: data) as? [MyModel] {
         list = items
    }
}, catch: { (exception: NSException) in
    print("Could not deserialize models.")
}, finally: nil)
Daniyar
  • 2,852
  • 2
  • 23
  • 38
7

What you are asking is not possible in Swift, as Swift has no facility to handle runtime errors such as out-of-bounds, access violations or failed forced unwrapping during runtime. Your application will terminate if any of these serious programming errors will occur.

Some pointers:

Long story short: Don't short-cut error handling in Swift. Play it safe, always.

Workaround: If you absolutely must catch runtime-errors, you must use process boundaries to guard. Run another program/process and communicate using pipes, sockets, etc.

Christopher Oezbek
  • 17,629
  • 3
  • 48
  • 71
6

I suspect that you'd like to catch errors which is not explicitly marked with "throws".

enter image description here

This makes no sense. You cannot catch other than errors which is explicitly marked with "throws". So, this warning is valid.

For this example, if executed, fatal error: Index out of range will occur. This is runtime error, and you cannot catch it.

For this example, you should check elements size like this, instead of doing try-catch error handling:

enter image description here

mono
  • 4,249
  • 2
  • 16
  • 36
4

There is a difference between ERRORS and EXCEPTIONS. Swift deals only with errors which are explicitly THROWN and has no native capability for dealing with EXCEPTIONS. As others have commented ERRORS must be thrown and you can't catch what isn't thrown.

By contrast Objective-C @try-@catch deals with exceptions, not errors,. Some objc methods may cause exceptions but do not declare them in any way to the compiler. e.g. FileHandle.write. Such exceptions are more closely aligned to Java's RuntimeException which also does not need to be declared.

There are some situations such as file handling where it would be nice to handle exceptions cleanly in Swift and it is possible by using an Objective-C wrapper. See http://stackoverflow.com/questions/34956002/how-to-properly-handle-nsfilehandle-exceptions-in-swift-2-0

Code reproduced here:

#ifndef ExceptionCatcher_h
#define ExceptionCatcher_h

#import <Foundation/Foundation.h>

NS_INLINE NSException * _Nullable tryBlock(void(^_Nonnull tryBlock)(void)) {
    @try {
        tryBlock();
    }
    @catch (NSException *exception) {
        return exception;
    }
    return nil;
}

#endif /* ExceptionCatcher_h */

Then calling it from Swift:

let exception = tryBlock {
   // execute dangerous code, e.g. write to a file handle
   filehandle.write(data)
}

if exception != nil {
   // deal with exception which is of type NSException
}
Dale
  • 2,855
  • 21
  • 25
1

As others mentioned, you should not catch these errors, you should fix them, but in case you want to execute more code before the program terminates, use NSSetUncaughtExceptionHandler in AppDelegate in applicationdidFinishLaunchingWithOptions function.

The function description:

Changes the top-level error handler.

Sets the top-level error-handling function where you can perform last-minute logging before the program terminates.

Community
  • 1
  • 1
inspector_60
  • 418
  • 1
  • 3
  • 12
-1

You simply cannot. The whole do-try-catch or do-catch statement is meant to be used to catch unhandled errors and...

I mean there is no point in catching error if no error occurs in first place... I see no scenario why would you want to do such thing, you make only compiler angry for no reason.

It's the same scenario if you safely unwrap optional with if let or guard let statements

guard let smth = smthOpt?.moreSpecific else { return }

//Compiler gives warning -  unused variable smth. You wouldn't declare the variable and then not use it, or you would? 

Simply Do-Catch is not meant to be used for safe use and I don't see any reason why to use it when not dealing with risky operations which need to catch...

for further understanding, see:

https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html

Dominik Bucher
  • 1,859
  • 2
  • 13
  • 22