Der folgende Code in Swift löst eine NSInvalidArgumentException-Ausnahme aus:
task = NSTask()
task.launchPath = "/SomeWrongPath"
task.launch()
Wie kann ich die Ausnahme abfangen? Soweit ich weiß, bezieht sich try / catch in Swift auf Fehler, die in Swift ausgelöst werden, nicht auf NSExceptions, die von Objekten wie NSTask (die vermutlich in ObjC geschrieben wurden) ausgelöst wurden. Ich bin neu bei Swift, also vermisse ich vielleicht etwas Offensichtliches ...
Bearbeiten : Hier ist ein Radar für den Fehler (speziell für NSTask): openradar.appspot.com/22837476
swift
cocoa-touch
exception
foundation
nsexception
Silyevsk
quelle
quelle
NSPredicate(fromMetadataQueryString:)
. Dies soll ein seininit?
. Wenn der String also schlecht ist, soll er wahrscheinlich zurückkehrennil
, aber tatsächlich stürzt er nur mit einer NSException ab.Antworten:
Hier ist ein Code, der NSExceptions in Swift 2-Fehler konvertiert.
Jetzt können Sie verwenden
do { try ObjC.catchException { /* calls that might throw an NSException */ } } catch { print("An error ocurred: \(error)") }
Objekt:
#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
Vergessen Sie nicht, dies zu Ihrem "* -Bridging-Header.h" hinzuzufügen:
#import "ObjC.h"
quelle
return
in den@catch
Block eingefügt habe ; bearbeitet, um das zu tun (und auch das Unnötige entferntlet error
, da der Fehler immer im allgemeinen catch-Block als ankommterror
)__attribute__((noescape))
den Typ hinzufügen ,tryBlock
sodass Sie Methoden aufrufen können,self
wenn Sie ihn verwenden, ohne ihnself
explizit einschließen zu müssen . Dies macht die vollständige Erklärung+ (BOOL)catchException:(__attribute__((noescape)) void(^)())tryBlock error:(__autoreleasing NSError **)error;
.Ich schlage vor, eine C-Funktion zu erstellen, die die Ausnahme abfängt und stattdessen einen NSError zurückgibt. Verwenden Sie dann diese Funktion.
Die Funktion könnte folgendermaßen aussehen:
NSError *tryCatch(void(^tryBlock)(), NSError *(^convertNSException)(NSException *)) { NSError *error = nil; @try { tryBlock(); } @catch (NSException *exception) { error = convertNSException(exception); } @finally { return error; } }
Und mit ein wenig Überbrückungshilfe müssen Sie nur anrufen:
if let error = tryCatch(task.launch, myConvertFunction) { print("An exception happened!", error.localizedDescription) // Do stuff } // Continue task
Hinweis: Ich habe es nicht wirklich getestet, ich konnte keinen schnellen und einfachen Weg finden, um Objective-C und Swift auf einem Spielplatz zu haben.
quelle
TL; DR: Verwenden Sie Karthago, um https://github.com/eggheadgames/SwiftTryCatch einzuschließen, oder CocoaPods, um https://github.com/ravero/SwiftTryCatch einzuschließen .
Dann können Sie Code wie diesen verwenden, ohne befürchten zu müssen, dass Ihre App abstürzt:
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 } }
Die von @BPCorp akzeptierte Antwort funktioniert wie beabsichtigt, aber wie wir festgestellt haben, werden die Dinge ein wenig interessant, wenn Sie versuchen, diesen Objective C-Code in ein mehrheitliches Swift-Framework zu integrieren und dann Tests ausführen. Wir hatten Probleme damit, dass die Klassenfunktion nicht gefunden wurde ( Fehler: Verwendung eines nicht aufgelösten Bezeichners ). Aus diesem Grund und nur zur allgemeinen Benutzerfreundlichkeit haben wir es als Karthago-Bibliothek für den allgemeinen Gebrauch verpackt.
Seltsamerweise konnten wir das Swift + ObjC-Framework problemlos an anderer Stelle verwenden. Es waren nur die Komponententests für das Framework, die Probleme hatten.
PRs angefordert! (Es wäre schön, wenn es eine Kombination aus CocoaPod und Karthago wäre und einige Tests hätte).
quelle
Wie in den Kommentaren erwähnt, ist es ein Fehler, dass diese API Ausnahmen für ansonsten wiederherstellbare Fehlerbedingungen auslöst. Legen Sie es ab und fordern Sie eine NSError-basierte Alternative an. Der aktuelle Stand der Dinge ist größtenteils ein Anachronismus, da die
NSTask
Daten vor Apple standardisiert wurden, dass Ausnahmen nur für Programmiererfehler gelten.In der Zwischenzeit können Sie zwar einen der Mechanismen aus anderen Antworten verwenden, um Ausnahmen in ObjC abzufangen und an Swift weiterzuleiten. Beachten Sie jedoch, dass dies nicht sehr sicher ist. Der Mechanismus zum Abwickeln von Stapeln hinter Ausnahmen von ObjC (und C ++) ist fragil und grundsätzlich nicht mit ARC kompatibel. Dies ist einer der Gründe, warum Apple Ausnahmen nur für Programmiererfehler verwendet. Die Idee ist, dass Sie (zumindest theoretisch) alle Ausnahmefälle in Ihrer App während der Entwicklung aussortieren können und keine Ausnahmen in Ihrem Produktionscode auftreten. (Schnelle Fehler oder
NSError
s können andererseits auf behebbare Situations- oder Benutzerfehler hinweisen.)Die sicherere Lösung besteht darin, die wahrscheinlichen Bedingungen vorauszusehen, unter denen eine API Ausnahmen auslösen und diese behandeln kann, bevor die API aufgerufen wird. Wenn Sie in ein indizieren
NSArray
, überprüfen Siecount
zuerst dessen . Wenn SielaunchPath
onNSTask
auf etwas setzen, das möglicherweise nicht vorhanden oder nicht ausführbar istNSFileManager
, überprüfen Sie dies, bevor Sie die Aufgabe starten.quelle
Sie können in Swift keine Objective-C-Ausnahme abfangen. Sie können dies jedoch umgehen, indem Sie einen Objective-C-Wrapper erstellen, den Sie dann in Swift importieren. Ich habe diese Arbeit erledigt und daraus ein wiederverwendbares Swift Package Manager-Paket gemacht. Fügen Sie dieses Paket einfach in Xcode hinzu und verwenden Sie es dann wie folgt:
import Foundation import ExceptionCatcher final class Foo: NSObject {} do { let value = try ExceptionCatcher.catch { return Foo().value(forKey: "nope") } print("Value:", value) } catch { print("Error:", error.localizedDescription) //=> Error: [valueForUndefinedKey:]: this class is not key value coding-compliant for the key nope. }
quelle
Die Version, die Fehlerinformationen etwas besser überträgt.
@implementation ObjCExceptionHandler + (BOOL)tryExecute:(nonnull void(NS_NOESCAPE^)(void))tryBlock error:(__autoreleasing NSError * _Nullable * _Nullable)error { @try { tryBlock(); return YES; } @catch (NSException *exception) { NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] init]; if (exception.userInfo != NULL) { userInfo = [[NSMutableDictionary alloc] initWithDictionary:exception.userInfo]; } if (exception.reason != nil) { if (![userInfo.allKeys containsObject:NSLocalizedFailureReasonErrorKey]) { [userInfo setObject:exception.reason forKey:NSLocalizedFailureReasonErrorKey]; } } *error = [[NSError alloc] initWithDomain:exception.name code:0 userInfo:userInfo]; return NO; } } @end
Anwendungsbeispiel:
let c = NSColor(calibratedWhite: 0.5, alpha: 1) var r: CGFloat = 0 var g: CGFloat = 0 var b: CGFloat = 0 var a: CGFloat = 0 do { try ObjC.perform { c.getRed(&r, green: &g, blue: &b, alpha: &a) } } catch { print(error) }
Vor:
Error Domain=NSInvalidArgumentException Code=0 "(null)"
Nach:
Error Domain=NSInvalidArgumentException Code=0 "*** -getRed:green:blue:alpha: not valid for the NSColor NSCalibratedWhiteColorSpace 0.5 1; need to first convert colorspace." UserInfo={NSLocalizedFailureReason=*** -getRed:green:blue:alpha: not valid for the NSColor NSCalibratedWhiteColorSpace 0.5 1; need to first convert colorspace.}
quelle
Wie in der Dokumentation angegeben , ist dies eine einfache und einfache Möglichkeit:
do { try fileManager.moveItem(at: fromURL, to: toURL) } catch let error as NSError { print("Error: \(error.domain)") }
quelle
@try
,@catch
, und@throw
Syntax, um nicht behebbare Programmiererfehler anzuzeigen. <...> Es gibt jedoch keine sichere Möglichkeit, Objective-C-Ausnahmen in Swift wiederherzustellen . Um Objective-C-Ausnahmen zu behandeln, schreiben Sie Objective-C-Code, der Ausnahmen abfängt, bevor sie Swift- Code erreichen . "