Come fornire una descrizione localizzata con un tipo di errore in Swift?


203

Sto definendo un tipo di errore personalizzato con la sintassi di Swift 3 e desidero fornire una descrizione intuitiva dell'errore restituito dalla localizedDescriptionproprietà Errordell'oggetto. Come posso farlo?

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.)"

Esiste un modo per localizedDescriptionrestituire la mia descrizione dell'errore personalizzata ("Una descrizione user-friendly dell'errore")? Si noti che l'oggetto errore qui è di tipo Errore non MyError. Ovviamente, posso lanciare l'oggetto su MyError

(error as? MyError)?.localizedDescription

ma c'è un modo per farlo funzionare senza trasmettere al mio tipo di errore?

Risposte:


403

Come descritto nelle note di rilascio di Xcode 8 beta 6,

I tipi di errore definiti rapidamente possono fornire descrizioni di errori localizzate adottando il nuovo protocollo LocalizedError.

Nel tuo caso:

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.

Puoi fornire ulteriori informazioni se l'errore viene convertito in NSError(che è sempre possibile):

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.")

Adottando il CustomNSErrorprotocollo, l'errore può fornire un userInfodizionario (e anche un domaine code). Esempio:

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

7
C'è un motivo per cui fai MyErrorun Errorprimo ed estendilo con LocalizedErrordopo? C'è una differenza se l'hai reso un LocalizedErrorprimo?
Gee.E,

9
@ Gee.E: non fa differenza. È solo un modo per organizzare il codice (un'estensione per ciascun protocollo). Confronta stackoverflow.com/questions/36263892/... , stackoverflow.com/questions/40502086/... o natashatherobot.com/using-swift-extensions .
Martin R

4
Ah, controlla. Ho capito cosa stai dicendo ora. La sezione "Conformità del protocollo" su natashatherobot.com/using-swift-extensions è davvero un buon esempio di cosa intendi. Grazie!
Gee.E,

1
@MartinR Se il mio errore viene convertito in NSError come posso passare un dizionario dall'errore a cui è possibile accedere come userInfo di NSError?
BangOperator

18
Fai attenzione a digitare var errorDescription: String?invece di String. C'è un bug nell'implementazione di LocalizedError. Vedi SR-5858 .
ethanhuang13,

35

Vorrei anche aggiungere, se il tuo errore ha parametri come questo

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

puoi chiamare questi parametri nella descrizione localizzata in questo modo:

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"
  }
}

Puoi anche accorciarlo in questo modo:

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

4

Esistono ora due protocolli di adozione di errori che il tipo di errore può adottare per fornire ulteriori informazioni a Objective-C - LocalizedError e CustomNSError. Ecco un errore di esempio che li adotta entrambi:

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" }

}

2
Puoi fare una modifica? I tuoi esempi non aiutano molto a capire il valore di ciascuno. O semplicemente cancellalo perché la risposta di MartinR lo offre esattamente ...
Honey,

3

L'uso di una struttura può essere un'alternativa. Un po 'di eleganza con localizzazione statica:

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)")
   }
}

0

Ecco una soluzione più elegante:

  enum ApiError: String, LocalizedError {

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

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

  }

4
Questo può essere più elegante in fase di esecuzione, ma il passaggio di localizzazione statica non riuscirà a estrarre queste stringhe per i traduttori; vedrai un "Bad entry in file – Argument is not a literal string"errore quando esegui exportLocalizationso genstringsper creare il tuo elenco di testo traducibile.
Savinola,

@savinola concorda, la localizzazione statica non funzionerà in questo caso. Forse l'utilizzo switch + caseè l'unica opzione ...
Vitaliy Gozhenko il

L'uso di valori non elaborati impedirà inoltre l'utilizzo dei valori associati per i tuoi errori
Brody Robertson,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.