wake-up-neo.net

Fehlerbehandlung in Swift-Language

Ich habe in Swift nicht zu viel gelesen, aber eines ist mir aufgefallen, dass es keine Ausnahmen gibt ... Wie funktioniert die Fehlerbehandlung in Swift? Hat jemand etwas gefunden, das mit der Fehlerbehandlung zusammenhängt?

181
peko

Swift 2 & 3

In Swift 2 hat sich etwas geändert, da es einen neuen Fehlerbehandlungsmechanismus gibt, der Ausnahmen etwas ähnlicher ist, sich aber im Detail unterscheidet.

1. Fehleranzeige möglich

Wenn die Funktion/Methode anzeigen soll, dass ein Fehler ausgegeben werden kann, sollte sie das throws-Schlüsselwort wie dieses enthalten 

func summonDefaultDragon() throws -> Dragon

Hinweis: Es gibt keine Spezifikation für die Fehlerart, die die Funktion tatsächlich auslösen kann. Diese Deklaration besagt lediglich, dass die Funktion eine Instanz eines beliebigen Typs, der ErrorType implementiert, auslösen kann oder überhaupt nicht auslöst.

2. Funktion aufrufen, die Fehler auslösen kann

Um die Funktion aufzurufen, müssen Sie das Schlüsselwort try verwenden

try summonDefaultDragon()

diese Zeile sollte normalerweise auf diese Weise vorhanden sein

do {
    let dragon = try summonDefaultDragon() 
} catch DragonError.dragonIsMissing {
    // Some specific-case error-handling
} catch DragonError.notEnoughMana(let manaRequired) {
    // Other specific-case error-handlng
} catch {
    // Catch all error-handling
}

Hinweis: catch-Klausel verwendet alle leistungsstarken Funktionen des Swift-Pattern-Matchings, damit Sie hier sehr flexibel sind.

Sie können sich dafür entscheiden, den Fehler weiterzugeben, wenn Sie eine auslösende Funktion von einer Funktion aufrufen, die selbst mit dem Schlüsselwort throws gekennzeichnet ist:

func fulfill(quest: Quest) throws {
    let dragon = try summonDefaultDragon()
    quest.ride(dragon)
} 

Alternativ können Sie die Wurffunktion mit try? aufrufen:

let dragonOrNil = try? summonDefaultDragon()

Auf diese Weise erhalten Sie entweder den Rückgabewert oder Null, wenn ein Fehler aufgetreten ist. Auf diese Weise erhalten Sie das Fehlerobjekt nicht.

Das bedeutet, dass Sie auch try? mit nützlichen Anweisungen kombinieren können, wie:

if let dragon = try? summonDefaultDragon()

oder

guard let dragon = try? summonDefaultDragon() else { ... }

Schließlich können Sie entscheiden, dass Sie wissen, dass ein Fehler tatsächlich nicht auftritt (z. B. weil Sie bereits Voraussetzungen geprüft haben) und das Schlüsselwort try! verwenden:

let dragon = try! summonDefaultDragon()

Wenn die Funktion tatsächlich einen Fehler ausgibt, wird in Ihrer Anwendung ein Laufzeitfehler angezeigt, und die Anwendung wird beendet.

3. Einen Fehler auslösen

Um einen Fehler auszulösen, verwenden Sie das Schlüsselwort throw

throw DragonError.dragonIsMissing

Sie können alles werfen, das dem ErrorType-Protokoll entspricht. Für Anfänger entspricht NSError diesem Protokoll, aber Sie möchten wahrscheinlich auf enum-basierte ErrorType gehen, mit der Sie mehrere verwandte Fehler gruppieren können, möglicherweise mit zusätzlichen Datenelementen

enum DragonError: ErrorType {
    case dragonIsMissing
    case notEnoughMana(requiredMana: Int)
    ...
}

Die Hauptunterschiede zwischen dem neuen Swift 2 & 3-Fehlermechanismus und den Ausnahmen im Java/C #/C++ - Stil sind folgende:

  • Die Syntax ist etwas anders: do-catch + try + defer im Vergleich zur traditionellen try-catch-finally-Syntax.
  • Die Ausnahmebehandlung hat im Ausnahmepfad normalerweise eine viel höhere Ausführungszeit als im Erfolgspfad. Dies ist bei Swift 2.0-Fehlern nicht der Fall, bei denen Erfolgsweg und Fehlerpfad ungefähr gleich teuer sind.
  • Der gesamte Fehler auslösende Code muss deklariert werden, während Ausnahmen möglicherweise von überall aus ausgelöst wurden. Alle Fehler sind in der Java-Nomenklatur "geprüfte Ausnahmen". Im Gegensatz zu Java geben Sie jedoch keine potenziell ausgelösten Fehler an.
  • Schnelle Ausnahmen sind nicht mit ObjC-Ausnahmen kompatibel. Ihr do-catch-Block wird keine NSException abfangen und umgekehrt. Dazu müssen Sie ObjC verwenden.
  • Swift-Ausnahmen sind mit Cocoa NSError Methodenkonventionen kompatibel, bei denen entweder false (für Bool zurückgegebene Funktionen) oder nil (für AnyObject zurückgegebene Funktionen) zurückgegeben werden und NSErrorPointer mit Fehlerdetails übergeben wird.

Als zusätzlichen Syntaxzucker zur Vereinfachung der Fehlerbehandlung gibt es zwei weitere Konzepte

  • verzögerte Aktionen (mit dem Schlüsselwort defer), mit denen Sie die gleiche Wirkung erzielen wie endgültige Blöcke in Java/C #/etc
  • guard-Anweisung (mit dem Schlüsselwort guard), mit der Sie etwas weniger if/else-Code schreiben können als im normalen Fehlerüberprüfungs-/Signalisierungscode.

Schnell 1

Laufzeitfehler:

Wie Leandros für die Behandlung von Laufzeitfehlern (wie Netzwerkverbindungsprobleme, Datenanalyse, Öffnen der Datei usw.) vorschlägt, sollten Sie NSError wie in ObjC verwenden, da Foundation, AppKit, UIKit usw. ihre Fehler auf diese Weise melden. Es ist also mehr Rahmensache als Sprachensache.

Ein anderes häufiges Muster, das verwendet wird, sind Trennungs- Erfolgs-/Fehlerblöcke wie in AFNetworking:

var sessionManager = AFHTTPSessionManager(baseURL: NSURL(string: "yavin4.yavin.planets"))
sessionManager.HEAD("/api/destoryDeathStar", parameters: xwingSquad,
    success: { (NSURLSessionDataTask) -> Void in
        println("Success")
    },
    failure:{ (NSURLSessionDataTask, NSError) -> Void in
        println("Failure")
    })

Dennoch erhielt der Fehlerblock häufig eine NSError-Instanz, die den Fehler beschreibt.

Programmierfehler:

Bei Programmierfehlern (z. B. Zugriff außerhalb des Bereichs auf ein Arrayelement, ungültige Argumente, die an einen Funktionsaufruf übergeben wurden usw.), haben Sie in ObjC Ausnahmen verwendet. Die Sprache für die schnelle Sprache scheint keine Sprachunterstützung für Ausnahmen zu haben (wie das Schlüsselwort throw, catch usw.). Wie aus der Dokumentation hervorgeht, läuft sie jedoch in derselben Laufzeit wie ObjC. Daher können Sie NSExceptions immer noch wie folgt werfen:

NSException(name: "SomeName", reason: "SomeReason", userInfo: nil).raise()

Sie können sie einfach nicht in reinem Swift fangen, obwohl Sie sich für Ausnahmen im ObjC-Code entscheiden können.

Die Frage ist, ob Sie Ausnahmen für Programmiererfehler auslösen oder Assertions verwenden sollten, wie von Apple im Sprachhandbuch vorgeschlagen.

142
MDJ

Update 9. Juni 2015 - Sehr wichtig

Swift 2.0 enthält die Schlüsselwörter try, throw und catch. Das aufregendste ist: 

Swift übersetzt Objective-C-Methoden, die Fehler erzeugen, automatisch in Methoden, die gemäß der systemeigenen Fehlerbehandlungsfunktionalität von Swift einen Fehler auslösen.

Anmerkung: Methoden, die Fehler verbrauchen, z. B. Delegieren von Methoden oder Methoden , die einen Beendigungshandler mit einem NSError-Objektargument benötigen, nicht werden Methoden, die werfen, wenn sie von Swift importiert werden.

Auszug aus: Apple Inc. „Swift mit Cocoa und Objective-C (Swift 2 Prerelease) verwenden.“ IBooks.

Beispiel: (aus dem Buch)

NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *URL = [NSURL fileURLWithPath:@"/path/to/file"];
NSError *error = nil;
BOOL success = [fileManager removeItemAtURL:URL error:&error];
if (!success && error){
    NSLog(@"Error: %@", error.domain);
}

Das Äquivalent in Swift wird sein:

let fileManager = NSFileManager.defaultManager()
let URL = NSURL.fileURLWithPath("path/to/file")
do {
    try fileManager.removeItemAtURL(URL)
} catch let error as NSError {
    print ("Error: \(error.domain)")
}

Fehler auslösen:

*errorPtr = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCannotOpenFile userInfo: nil]

Wird automatisch an den Anrufer weitergegeben:

throw NSError(domain: NSURLErrorDomain, code: NSURLErrorCannotOpenFile, userInfo: nil)

Aus Apple-Büchern, The Swift Programming Language, scheint es, dass Fehler mit enum behandelt werden sollten.

Hier ist ein Beispiel aus dem Buch.

enum ServerResponse {
    case Result(String, String)
    case Error(String)
}

let success = ServerResponse.Result("6:00 am", "8:09 pm")
let failure = ServerResponse.Error("Out of cheese.")

switch success {
case let .Result(sunrise, sunset):
    let serverResponse = "Sunrise is at \(sunrise) and sunset is at \(sunset)."
case let .Error(error):
    let serverResponse = "Failure...  \(error)"
}

Von: Apple Inc. „Die schnelle Programmiersprache“. IBooks. https://itun.es/br/jEUH0.l

Update

Aus Apple-Nachrichtenbüchern "Verwenden von Swift mit Kakao und Objective-C". Laufzeit-Ausnahmen treten bei Verwendung von Swift-Sprachen nicht auf, daher gibt es keinen Try-Catch. Verwenden Sie stattdessen Optionale Verkettung .

Hier ist eine Strecke aus dem Buch:

In der folgenden Codeliste lautet die erste und die zweite Zeile wird nicht ausgeführt, da die length-Eigenschaft und der characterAtIndex: Methode existiert nicht für ein NSDate-Objekt. Die myLength-Konstante ist wurde als optionaler Int abgeleitet und auf null gesetzt. Sie können auch eine .__ verwenden. if-let-Anweisung, um das Ergebnis einer Methode, die Das Objekt antwortet möglicherweise nicht, wie in Zeile 3 gezeigt

let myLength = myObject.length?
let myChar = myObject.characterAtIndex?(5)
if let fifthCharacter = myObject.characterAtIndex(5) {
    println("Found \(fifthCharacter) at index 5")
}

Auszug aus: Apple Inc. „Swift mit Cocoa und Objective-C verwenden.“ IBooks. https://itun.es/br/1u3-0.l


Außerdem empfehlen die Bücher, das Kakaofehlermuster von Objective-C (NSError Object) zu verwenden. 

Die Fehlerberichterstattung in Swift folgt dem gleichen Muster wie in Ziel-C, mit dem zusätzlichen Vorteil, eine optionale Rendite anzubieten Werte. Im einfachsten Fall geben Sie einen Bool-Wert aus der .__ zurück. Funktion, um anzuzeigen, ob es erfolgreich war oder nicht. Wann müssen Sie Wenn Sie den Grund für den Fehler melden, können Sie der Funktion eine .__ hinzufügen. NSError out-Parameter des Typs NSErrorPointer. Dieser Typ ist ungefähr entspricht NSError ** von Objective-C mit zusätzlicher Speichersicherheit und optionales Tippen. Sie können den Präfix & Operator verwenden, um ein .__ zu übergeben. Verweis auf einen optionalen NSError-Typ als NSErrorPointer-Objekt als in der nachstehenden Codeliste aufgeführt.

var writeError : NSError?
let written = myString.writeToFile(path, atomically: false,
    encoding: NSUTF8StringEncoding,
    error: &writeError)
if !written {
    if let error = writeError {
        println("write failure: \(error.localizedDescription)")
    }
}

Auszug aus: Apple Inc. „Swift mit Cocoa und Objective-C verwenden.“ IBooks. https://itun.es/br/1u3-0.l

In Swift gibt es keine Ausnahmen, ähnlich wie bei Objective-C.

In der Entwicklung können Sie assert verwenden, um mögliche Fehler abzufangen, die vor dem Produktionsstart behoben werden müssen.

Die klassische NSError-Methode wird nicht geändert. Sie senden eine NSErrorPointer, die aufgefüllt wird.

Kurzes Beispiel: 

var error: NSError?
var contents = NSFileManager.defaultManager().contentsOfDirectoryAtPath("/Users/leandros", error: &error)
if let error = error {
    println("An error occurred \(error)")
} else {
    println("Contents: \(contents)")
}
13
Leandros

Der empfohlene "schnelle Weg" lautet:

func write(path: String)(#error: NSErrorPointer) -> Bool { // Useful to curry error parameter for retrying (see below)!
    return "Hello!".writeToFile(path, atomically: false, encoding: NSUTF8StringEncoding, error: error)
}

var writeError: NSError?
let written = write("~/Error1")(error: &writeError)
if !written {
    println("write failure 1: \(writeError!.localizedDescription)")
    // assert(false) // Terminate program
}

Allerdings bevorzuge ich try/catch, da ich es einfacher finde, zu folgen, da die Fehlerbehandlung am Ende in einen separaten Block verschoben wird. Diese Anordnung wird manchmal als "Golden Path" bezeichnet. Zum Glück kannst du das mit Verschlüssen machen:

TryBool {
    write("~/Error2")(error: $0) // The code to try
}.catch {
    println("write failure 2: \($0!.localizedDescription)") // Report failure
    // assert(false) // Terminate program
}

Es ist auch leicht, eine Wiederholungsfunktion hinzuzufügen:

TryBool {
    write("~/Error3")(error: $0) // The code to try
}.retry {
    println("write failure 3 on try \($1 + 1): \($0!.localizedDescription)")
    return write("~/Error3r")  // The code to retry
}.catch {
    println("write failure 3 catch: \($0!.localizedDescription)") // Report failure
    // assert(false) // Terminate program
}

Die Auflistung für TryBool ist:

class TryBool {
    typealias Tryee = NSErrorPointer -> Bool
    typealias Catchee = NSError? -> ()
    typealias Retryee = (NSError?, UInt) -> Tryee

    private var tryee: Tryee
    private var retries: UInt = 0
    private var retryee: Retryee?

    init(tryee: Tryee) {
        self.tryee = tryee
    }

    func retry(retries: UInt, retryee: Retryee) -> Self {
        self.retries = retries
        self.retryee = retryee
        return self
    }
    func retry(retryee: Retryee) -> Self {
        return self.retry(1, retryee)
    }
    func retry(retries: UInt) -> Self {
        // For some reason you can't write the body as "return retry(1, nil)", the compiler doesn't like the nil
        self.retries = retries
        retryee = nil
        return self
    }
    func retry() -> Self {
        return retry(1)
    }

    func catch(catchee: Catchee) {
        var error: NSError?
        for numRetries in 0...retries { // First try is retry 0
            error = nil
            let result = tryee(&error)
            if result {
                return
            } else if numRetries != retries {
                if let r = retryee {
                    tryee = r(error, numRetries)
                }
            }
        }
        catchee(error)
    }
}

Sie können eine ähnliche Klasse zum Testen eines optionalen Rückgabewerts anstelle des Bool-Werts schreiben:

class TryOptional<T> {
    typealias Tryee = NSErrorPointer -> T?
    typealias Catchee = NSError? -> T
    typealias Retryee = (NSError?, UInt) -> Tryee

    private var tryee: Tryee
    private var retries: UInt = 0
    private var retryee: Retryee?

    init(tryee: Tryee) {
        self.tryee = tryee
    }

    func retry(retries: UInt, retryee: Retryee) -> Self {
        self.retries = retries
        self.retryee = retryee
        return self
    }
    func retry(retryee: Retryee) -> Self {
        return retry(1, retryee)
    }
    func retry(retries: UInt) -> Self {
        // For some reason you can't write the body as "return retry(1, nil)", the compiler doesn't like the nil
        self.retries = retries
        retryee = nil
        return self
    }
    func retry() -> Self {
        return retry(1)
    }

    func catch(catchee: Catchee) -> T {
        var error: NSError?
        for numRetries in 0...retries {
            error = nil
            let result = tryee(&error)
            if let r = result {
                return r
            } else if numRetries != retries {
                if let r = retryee {
                    tryee = r(error, numRetries)
                }
            }
        }
        return catchee(error)
    }
}

Die TryOptional-Version erzwingt einen nicht optionalen Rückgabetyp, der die nachfolgende Programmierung vereinfacht, z. "Schneller Weg:

struct FailableInitializer {
    init?(_ id: Int, error: NSErrorPointer) {
        // Always fails in example
        if error != nil {
            error.memory = NSError(domain: "", code: id, userInfo: [:])
        }
        return nil
    }
    private init() {
        // Empty in example
    }
    static let fallback = FailableInitializer()
}

func failableInitializer(id: Int)(#error: NSErrorPointer) -> FailableInitializer? { // Curry for retry
    return FailableInitializer(id, error: error)
}

var failError: NSError?
var failure1Temp = failableInitializer(1)(error: &failError)
if failure1Temp == nil {
    println("failableInitializer failure code: \(failError!.code)")
    failure1Temp = FailableInitializer.fallback
}
let failure1 = failure1Temp! // Unwrap

TryOptional verwenden:

let failure2 = TryOptional {
    failableInitializer(2)(error: $0)
}.catch {
    println("failableInitializer failure code: \($0!.code)")
    return FailableInitializer.fallback
}

let failure3 = TryOptional {
    failableInitializer(3)(error: $0)
}.retry {
    println("failableInitializer failure, on try \($1 + 1), code: \($0!.code)")
    return failableInitializer(31)
}.catch {
    println("failableInitializer failure code: \($0!.code)")
    return FailableInitializer.fallback
}

Beachten Sie das automatische Auspacken.

11
Howard Lovatt

Edit: Obwohl diese Antwort funktioniert, ist es kaum mehr als Objective-C, das in Swift transliteriert wird. Es wurde durch Änderungen in Swift 2.0 überholt. Die Antwort von Guilherme Torres Castro ist eine sehr gute Einführung in die bevorzugte Art der Fehlerbehandlung in Swift. VOS

Ich brauchte ein bisschen, um es herauszufinden, aber ich glaube, ich habe es aufgedeckt. Es scheint jedoch hässlich. Nichts weiter als eine dünne Haut über der Objective-C-Version.

Aufruf einer Funktion mit einem NSError-Parameter ...

var fooError : NSError ? = nil

let someObject = foo(aParam, error:&fooError)

// Check something was returned and look for an error if it wasn't.
if !someObject {
   if let error = fooError {
      // Handle error
      NSLog("This happened: \(error.localizedDescription)")
   }
} else {
   // Handle success
}`

Schreiben der Funktion, die einen Fehlerparameter annimmt ...

func foo(param:ParamObject, error: NSErrorPointer) -> SomeObject {

   // Do stuff...

   if somethingBadHasHappened {
      if error {
         error.memory = NSError(domain: domain, code: code, userInfo: [:])
      }
      return nil
   }

   // Do more stuff...
}
7

Grundlegender Wrapper für Objective C, der Ihnen die Try-Catch-Funktion gibt . https://github.com/williamFalcon/SwiftTryCatch

Verwenden Sie wie:

SwiftTryCatch.try({ () -> Void in
        //try something
     }, catch: { (error) -> Void in
        //handle error
     }, finally: { () -> Void in
        //close resources
})
4
William Falcon

Dies ist eine Update-Antwort für Swift 2.0. Ich freue mich auf ein umfangreiches Fehlerbehandlungsmodell wie in Java. Schließlich gaben sie die guten Nachrichten bekannt. Hier

Fehlerbehandlungsmodell: Das neue Fehlerbehandlungsmodell in Swift 2.0 wird Fühlen Sie sich sofort natürlich mit bekannten try, throw und catch-Schlüsselwörtern . Das Beste ist, dass es perfekt auf die Apple SDKs und .__ abgestimmt ist. NSError. Tatsächlich entspricht NSError dem ErrorType von Swift. Du wirst Ich möchte auf jeden Fall die WWDC-Sitzung über Neuerungen in. erfahren Sie mehr darüber.

z.B :

func loadData() throws { }
func test() {
do {
    try loadData()
} catch {
    print(error)
}}

Wie Guilherme Torres Castro sagte, können in Swift 2.0 try, catch, do in der Programmierung verwendet werden.

In der CoreData-Methode zum Abrufen von Daten anstelle von &error als Parameter in managedContext.executeFetchRequest(fetchRequest, error: &error) müssen wir nur noch use managedContext.executeFetchRequest(fetchRequest) verwenden und dann den Fehler mit try, catch behandeln ( Apple Document Link ). 

do {
   let fetchedResults = try managedContext.executeFetchRequest(fetchRequest) as? [NSManagedObject]
   if let results = fetchedResults{
      people = results
   }
} catch {
   print("Could not fetch")
}

Wenn Sie die xcode7-Beta bereits heruntergeladen haben. Versuchen Sie, werfen Fehler in Documentations and API Reference zu suchen, und wählen Sie das erste Ergebnis aus. Es gibt eine grundlegende Vorstellung davon, was für diese neue Syntax getan werden kann. Eine vollständige Dokumentation ist jedoch für viele APIs noch nicht veröffentlicht. 

Weitere ausgefallene Fehlerbehandlungstechniken finden Sie in 

Was ist neu in Swift (2015 Session 106 28m30s)

3
Zingoer

Fehlerbehandlung ist eine neue Funktion von Swift 2.0. Es verwendet die Schlüsselwörter try, throw und catch.

Siehe die Apple Swift 2.0-Ankündigung auf dem offiziellen Apple Swift-Blog

2
babelchips

Beginnend mit Swift 2 wird die Fehlerbehandlung, wie andere bereits erwähnt haben, am besten durch die Verwendung von do/try/catch und ErrorType-Enummen erreicht. Dies funktioniert ziemlich gut für synchrone Methoden, aber für die asynchrone Fehlerbehandlung ist etwas Klugheit erforderlich.

Dieser Artikel hat einen großartigen Ansatz für dieses Problem:

https://jeremywsherman.com/blog/2015/06/17/ using-Swift-throws-mit-completion-callbacks/

Zusammenfassen:

// create a typealias used in completion blocks, for cleaner code
typealias LoadDataResult = () throws -> NSData

// notice the reference to the typealias in the completionHandler
func loadData(someID: String, completionHandler: LoadDataResult -> Void)
    {
    completionHandler()
    }

der Aufruf der obigen Methode wäre dann wie folgt:

self.loadData("someString",
    completionHandler:     
        { result: LoadDataResult in
        do
            {
            let data = try result()
            // success - go ahead and work with the data
            }
        catch
            {
            // failure - look at the error code and handle accordingly
            }
        })

Dies scheint ein wenig sauberer zu sein, als wenn ein separater errorHandler-Callback an die asynchrone Funktion übergeben wurde. Auf diese Weise würde dies vor Swift 2 behandelt werden.

1
Gene Loparco

Was ich gesehen habe, ist, dass Sie aufgrund der Art des Geräts nicht eine Reihe von kryptischen Fehlernachrichten an den Benutzer werfen möchten. Aus diesem Grund geben die meisten Funktionen optionale Werte zurück. Dann müssen Sie nur die optionalen Werte ignorieren. Wenn eine Funktion nil zurückkommt, was bedeutet, dass sie fehlgeschlagen ist, können Sie eine Nachricht oder was auch immer platzieren.

0
cheborneck

Schöne und einfache lib, um Ausnahme zu behandeln: TryCatchFinally-Swift

Wie einige andere umfasst es die Ausnahmefunktionen von Objective C.

Verwenden Sie es so:

try {
    println("  try")
}.catch { e in
    println("  catch")
}.finally {
    println("  finally")
}
0