Swift Error Handling For Methods That Do Not Throw [duplicate]

不想你离开。 提交于 2019-12-01 02:24:51

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)

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.

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

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:

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
}

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.

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!