Поймать NSException в Swift

Следующий код в Swift вызывает исключение NSInvalidArgumentException:

task = NSTask()
task.launchPath = "/SomeWrongPath"
task.launch()

Как я могу поймать исключение? Насколько я понимаю, try/catch в Swift - это ошибки, возникающие в Swift, а не для NSExceptions, созданных из таких объектов, как NSTask (что, я думаю, написано в ObjC). Я новичок в Swift, может быть, мне не хватает чего-то очевидного...

Изменить: здесь радар для ошибки (специально для NSTask): openradar.appspot.com/22837476

Ответ 1

Вот некоторый код, который преобразует исключения NSEx в ошибки Swift 2.

Теперь вы можете использовать

do {
    try ObjC.catchException {

       /* calls that might throw an NSException */
    }
}
catch {
    print("An error ocurred: \(error)")
}

ObjC.h:

#import <Foundation/Foundation.h>

@interface ObjC : NSObject

+ (BOOL)catchException:(void(^)(void))tryBlock error:(__autoreleasing NSError **)error;

@end

ObjC.m

#import "ObjC.h"

@implementation ObjC 

+ (BOOL)catchException:(void(^)(void))tryBlock error:(__autoreleasing NSError **)error {
    @try {
        tryBlock();
        return YES;
    }
    @catch (NSException *exception) {
        *error = [[NSError alloc] initWithDomain:exception.name code:0 userInfo:exception.userInfo];
        return NO;
    }
}

@end

Не забудьте добавить это в ваш "* -Bridging-Header.h":

#import "ObjC.h"

Ответ 2

Я предлагаю сделать C-функцию, которая поймает исключение и вернет NSError. Затем используйте эту функцию.

Функция может выглядеть так:

NSError *tryCatch(void(^tryBlock)(), NSError *(^convertNSException)(NSException *))
{
    NSError *error = nil;
    @try {
        tryBlock();
    }
    @catch (NSException *exception) {
        error = convertNSException(exception);
    }
    @finally {
        return error;
    }
}

И с небольшой помощью по мосту вам просто нужно позвонить:

if let error = tryCatch(task.launch, myConvertFunction) {
    print("An exception happened!", error.localizedDescription)
    // Do stuff
}
// Continue task

Примечание. Я действительно не тестировал его, я не мог найти быстрый и простой способ иметь Objective-C и Swift на игровой площадке.

Ответ 3

TL; DR: используйте Carthage для включения https://github.com/eggheadgames/SwiftTryCatch или CocoaPods для включения https://github.com/ravero/SwiftTryCatch.

Затем вы можете использовать такой код, не опасаясь, что это приведет к краху вашего приложения:

import Foundation
import SwiftTryCatch

class SafeArchiver {

    class func unarchiveObjectWithFile(filename: String) -> AnyObject? {

        var data : AnyObject? = nil

        if NSFileManager.defaultManager().fileExistsAtPath(filename) {
            SwiftTryCatch.tryBlock({
                data = NSKeyedUnarchiver.unarchiveObjectWithFile(filename)
                }, catchBlock: { (error) in
                    Logger.logException("SafeArchiver.unarchiveObjectWithFile")
                }, finallyBlock: {
            })
        }
        return data
    }

    class func archiveRootObject(data: AnyObject, toFile : String) -> Bool {
        var result: Bool = false

        SwiftTryCatch.tryBlock({
            result =  NSKeyedArchiver.archiveRootObject(data, toFile: toFile)
            }, catchBlock: { (error) in
                Logger.logException("SafeArchiver.archiveRootObject")
            }, finallyBlock: {
        })
        return result
    }
}

Принятый ответ @BPCorp работает по назначению, но, как мы выяснили, все становится немного интересным, если вы попытаетесь включить этот код Objective C в структуру Swift большинства и затем выполните тесты. У нас были проблемы с отсутствием функции класса (Ошибка: использование неразрешенного идентификатора). Поэтому, по этой причине, и просто общая простота использования, мы упаковали его как библиотеку Carthage для общего использования.

Как ни странно, мы могли бы использовать среду Swift + ObjC в других местах без проблем, это были только модульные тесты для фреймворка, который боролся.

Запрошено PR! (Было бы неплохо иметь его комбо CocoaPod и Carthage build, а также иметь некоторые тесты).

Ответ 4

Как отмечалось в комментариях, этот API генерирует исключения для ситуаций, которые могут быть восстановлены в противном случае, является ошибкой. Отредактируйте его и попросите альтернативу, основанную на NSError. В основном текущее состояние дел является анахронизмом, поскольку NSTask датируется до того, как Apple стандартизировала при наличии исключений только для ошибок программиста.

Тем временем, хотя вы можете использовать один из механизмов из других ответов для исключения исключений в ObjC и передать их Swift, имейте в виду, что делать это не очень безопасно. Механизм разворачивания стека за исключениями ObjC (и С++) является хрупким и принципиально несовместимым с ARC. Это часть того, почему Apple использует исключения только для ошибок программиста - идея состоит в том, что вы можете (теоретически, по крайней мере) разобрать все случаи исключения в своем приложении во время разработки и не иметь никаких исключений, возникающих в вашем производственном коде. (Быстрые ошибки или NSError s, с другой стороны, могут указывать восстановимые ситуационные или пользовательские ошибки.)

Более безопасное решение - предусмотреть вероятные условия, которые могут заставить API выдавать исключения и обрабатывать их перед вызовом API. Если вы индексируете в NSArray, сначала проверьте его count. Если вы установите launchPath на NSTask на то, что может не существовать или может не выполняться, используйте NSFileManager, чтобы проверить, что перед запуском задачи.

Ответ 5

Как указано в документации, это простой и легкий способ сделать это:

do {
    try fileManager.moveItem(at: fromURL, to: toURL)
} catch let error as NSError {
    print("Error: \(error.domain)")
}