2015-09-24 2 views
44

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

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

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

Edit: вот радар для ошибки (в частности, для NSTask): openradar.appspot.com/22837476

+0

К сожалению, вы не можете перехватывать исключения Objective-C в Swift, см., Например, http://stackoverflow.com/questions/24023112/try-catch-exceptions-in-swift. Можно считать ошибкой, что NSTask выдает исключения вместо того, чтобы возвращать ошибки, и вы можете подать отчет об ошибке, но я сомневаюсь, что Apple изменит API. –

+0

Спасибо @MartinR. Я думаю, что это либо ошибка в API, либо Swift должен обеспечить механизм для исключения исключений ObjC (или лучше того и другого) ... В любом случае, я открыл ошибку (https://openradar.appspot.com/22837476) , хотя, я думаю, существует еще много методов API с той же проблемой. – silyevsk

+0

Примером для меня был «NSPredicate (fromMetadataQueryString:)». Предполагается, что это 'init?', Поэтому, если строка плохая, она, вероятно, предназначена для возврата 'nil', но на самом деле это просто сбой с NSException. – matt

ответ

78

Вот код, который преобразует NSExceptions в 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(^)())tryBlock error:(__autoreleasing NSError **)error; 

@end 

ObjC.m

#import "ObjC.h" 

@implementation ObjC 

+ (BOOL)catchException:(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" 
+0

Я столкнулся с этой ошибкой в ​​последнем Swift (2.2, я думаю). Быстрый стреловидный блок всегда срабатывает. Однако, если на самом деле нет исключения, ошибка выводится как «Foundation._GenericObjCError.NilError». Теперь смотрим в решение. –

+1

Понял! Вам нужно что-то вернуть после успешного выполнения tryBlock(), или он всегда попадает в блок catch. Я дважды проверил, что он по-прежнему правильно срабатывает при реальном исключении. Я отредактирую ваш ответ, чтобы включить это изменение. –

+0

ты спас мой день –

11

То, что я предлагаю, чтобы сделать 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 на игровой площадке.

8

TL; DR: Используйте Карфаген включить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, а также некоторые тесты).

+1

Для STC я бы рекомендовал просто копировать файлы в ваш проект. У меня возникло слишком много проблем с попыткой импорта и обновления STC через менеджеров пакетов. Там около 173 разных вилок. Они поддерживают Swift 1, Swift 2 и/или Swift 3. Они поддерживают Карфаген, CocoaPods и/или SPM. Они поддерживают iOS, macOS, tvOS и/или watchOS. Но ни одна вилка не поддерживает конкретную комбинацию вещей, которые мне нужны (или обновлялись через год), и я потратил слишком много времени на поиск. Выберите один, скопируйте его в свой проект и приступите к более полезной работе. – Ssswift

1

Как отмечено в комментариях, этот API генерирует исключения для аварийных ситуаций, восстанавливаемых в противном случае, является ошибкой. File it, and request an NSError-based alternative. В основном текущее положение дел является анахронизмом, так как NSTask датируется до того, как Apple стандартизируется с исключениями только для ошибок программиста.

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

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

+0

Я действительно открыл ошибку еще в сентябре (она появляется в комментариях, я добавлю ее самому вопросу) – silyevsk

+0

Как вы предвидите неправильный тип? – mdang