Wie kann eine lokalisierte Beschreibung mit einem Fehlertyp in Swift bereitgestellt werden?

202

Ich definiere einen benutzerdefinierten Fehlertyp mit Swift 3-Syntax und möchte eine benutzerfreundliche Beschreibung des Fehlers bereitstellen, der von der localizedDescriptionEigenschaft des ErrorObjekts zurückgegeben wird. Wie kann ich es tun?

public enum MyError: Error {
  case customError

  var localizedDescription: String {
    switch self {
    case .customError:
      return NSLocalizedString("A user-friendly description of the error.", comment: "My error")
    }
  }
}

let error: Error = MyError.customError
error.localizedDescription
// "The operation couldn’t be completed. (MyError error 0.)"

Gibt es eine Möglichkeit localizedDescription, meine benutzerdefinierte Fehlerbeschreibung zurückzugeben ("Eine benutzerfreundliche Beschreibung des Fehlers.")? Beachten Sie, dass das Fehlerobjekt hier vom Typ ist Errorund nicht MyError. Ich kann das Objekt natürlich in MyError umwandeln

(error as? MyError)?.localizedDescription

Aber gibt es eine Möglichkeit, es zum Laufen zu bringen, ohne auf meinen Fehlertyp umzusteigen?

Evgenii
quelle

Antworten:

400

Wie in den Versionshinweisen zu Xcode 8 Beta 6 beschrieben,

Schnell definierte Fehlertypen können lokalisierte Fehlerbeschreibungen bereitstellen, indem das neue LocalizedError-Protokoll übernommen wird.

In deinem Fall:

public enum MyError: Error {
    case customError
}

extension MyError: LocalizedError {
    public var errorDescription: String? {
        switch self {
        case .customError:
            return NSLocalizedString("A user-friendly description of the error.", comment: "My error")
        }
    }
}

let error: Error = MyError.customError
print(error.localizedDescription) // A user-friendly description of the error.

Sie können noch mehr Informationen bereitstellen, wenn der Fehler in konvertiert wird NSError(was immer möglich ist):

extension MyError : LocalizedError {
    public var errorDescription: String? {
        switch self {
        case .customError:
            return NSLocalizedString("I failed.", comment: "")
        }
    }
    public var failureReason: String? {
        switch self {
        case .customError:
            return NSLocalizedString("I don't know why.", comment: "")
        }
    }
    public var recoverySuggestion: String? {
        switch self {
        case .customError:
            return NSLocalizedString("Switch it off and on again.", comment: "")
        }
    }
}

let error = MyError.customError as NSError
print(error.localizedDescription)        // I failed.
print(error.localizedFailureReason)      // Optional("I don\'t know why.")
print(error.localizedRecoverySuggestion) // Optional("Switch it off and on again.")

Durch die Übernahme des CustomNSErrorProtokolls kann der Fehler ein userInfoWörterbuch (und auch ein domainund code) bereitstellen . Beispiel:

extension MyError: CustomNSError {

    public static var errorDomain: String {
        return "myDomain"
    }

    public var errorCode: Int {
        switch self {
        case .customError:
            return 999
        }
    }

    public var errorUserInfo: [String : Any] {
        switch self {
        case .customError:
            return [ "line": 13]
        }
    }
}

let error = MyError.customError as NSError

if let line = error.userInfo["line"] as? Int {
    print("Error in line", line) // Error in line 13
}

print(error.code) // 999
print(error.domain) // myDomain
Martin R.
quelle
7
Gibt es einen Grund, warum Sie MyErroreine Errorerste machen und sie LocalizedErrorspäter erweitern? Gibt es einen Unterschied, ob Sie es überhaupt geschafft LocalizedErrorhaben?
Gee.E
9
@ Gee.E: Es macht keinen Unterschied. Dies ist nur eine Möglichkeit, den Code zu organisieren (eine Erweiterung für jedes Protokoll). Vergleichen Sie stackoverflow.com/questions/36263892/… , stackoverflow.com/questions/40502086/… oder natashatherobot.com/using-swift-extensions .
Martin R
4
Ah, überprüfe. Ich verstehe, was du jetzt sagst. Der Abschnitt "Protokollkonformität" auf natashatherobot.com/using-swift-extensions ist in der Tat ein gutes Beispiel dafür, was Sie meinen. Vielen Dank!
Gee.E
1
@MartinR Wenn mein Fehler in NSError konvertiert würde, wie kann ich aus einem Fehler ein Wörterbuch übergeben, auf das als userInfo von NSError zugegriffen werden kann?
BangOperator
18
Passen Sie auf, var errorDescription: String?anstatt zu tippen String. Es gibt einen Fehler in der Implementierung von LocalizedError. Siehe SR-5858 .
Ethanhuang13
35

Ich würde auch hinzufügen, wenn Ihr Fehler solche Parameter hat

enum NetworkError: LocalizedError {
  case responseStatusError(status: Int, message: String)
}

Sie können diese Parameter in Ihrer lokalisierten Beschreibung folgendermaßen aufrufen:

extension NetworkError {
  public var errorDescription: String? {
    switch self {
    case .responseStatusError(status: let status, message: let message):
      return "Error with status \(status) and message \(message) was thrown"
  }
}

Sie können dies sogar so kürzer machen:

extension NetworkError {
  public var errorDescription: String? {
    switch self {
    case let .responseStatusError(status, message):
      return "Error with status \(status) and message \(message) was thrown"
  }
}
Reza Shirazian
quelle
4

Es gibt jetzt zwei fehlerübergreifende Protokolle, die Ihr Fehlertyp übernehmen kann, um Objective-C zusätzliche Informationen bereitzustellen - LocalizedError und CustomNSError. Hier ist ein Beispielfehler, der beide übernimmt:

enum MyBetterError : CustomNSError, LocalizedError {
    case oops

    // domain
    static var errorDomain : String { return "MyDomain" }
    // code
    var errorCode : Int { return -666 }
    // userInfo
    var errorUserInfo: [String : Any] { return ["Hey":"Ho"] };

    // localizedDescription
    var errorDescription: String? { return "This sucks" }
    // localizedFailureReason
    var failureReason: String? { return "Because it sucks" }
    // localizedRecoverySuggestion
    var recoverySuggestion: String? { return "Give up" }

}
matt
quelle
2
Können Sie eine Bearbeitung vornehmen? Ihre Beispiele helfen nicht viel, um den Wert jedes einzelnen zu verstehen. Oder löschen Sie es einfach, weil MartinRs Antwort genau dies bietet ...
Honey
3

Die Verwendung einer Struktur kann eine Alternative sein. Ein bisschen Eleganz mit statischer Lokalisierung:

import Foundation

struct MyError: LocalizedError, Equatable {

   private var description: String!

   init(description: String) {
       self.description = description
   }

   var errorDescription: String? {
       return description
   }

   public static func ==(lhs: MyError, rhs: MyError) -> Bool {
       return lhs.description == rhs.description
   }
}

extension MyError {

   static let noConnection = MyError(description: NSLocalizedString("No internet connection",comment: ""))
   static let requestFailed = MyError(description: NSLocalizedString("Request failed",comment: ""))
}

func throwNoConnectionError() throws {
   throw MyError.noConnection
}

do {
   try throwNoConnectionError()
}
catch let myError as MyError {
   switch myError {
   case .noConnection:
       print("noConnection: \(myError.localizedDescription)")
   case .requestFailed:
       print("requestFailed: \(myError.localizedDescription)")
   default:
      print("default: \(myError.localizedDescription)")
   }
}
Zafer Sevik
quelle
0

Hier ist eine elegantere Lösung:

  enum ApiError: String, LocalizedError {

    case invalidCredentials = "Invalid credentials"
    case noConnection = "No connection"

    var localizedDescription: String { return NSLocalizedString(self.rawValue, comment: "") }

  }
Vitaliy Gozhenko
quelle
4
Dies mag zur Laufzeit eleganter sein, aber der statische Lokalisierungsschritt kann diese Zeichenfolgen für Übersetzer nicht extrahieren. "Bad entry in file – Argument is not a literal string"Beim Ausführen exportLocalizationsoder genstringsErstellen einer Liste mit übersetzbarem Text wird ein Fehler angezeigt.
Savinola
@savinola stimmt zu, statische Lokalisierung wird in einem solchen Fall nicht funktionieren. Vielleicht ist die Verwendung switch + casenur Option ...
Vitaliy Gozhenko
Die Verwendung von Rohwerten verhindert auch die Verwendung zugehöriger Werte für einen Ihrer Fehler
Brody Robertson