Как предоставить локализованное описание с типом ошибки в Swift?

Я определяю настраиваемый тип ошибки с синтаксисом Swift 3 и хочу предоставить удобное для пользователя описание ошибки, возвращаемой свойством localizedDescription объекта Error. Как я могу это сделать?

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

Есть ли способ для localizedDescription вернуть мое собственное описание ошибки («Удобное для пользователя описание ошибки»)? Обратите внимание, что объект ошибки здесь имеет тип Error, а не MyError. Я, конечно, могу передать объект в MyError

(error as? MyError)?.localizedDescription

но есть ли способ заставить его работать без преобразования в мой тип ошибки?


person Evgenii    schedule 27.08.2016    source источник


Ответы (6)


Как описано в примечаниях к выпуску Xcode 8 beta 6,

Типы ошибок, определенные в Swift, могут предоставлять локализованные описания ошибок, используя новый протокол LocalizedError.

В твоем случае:

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.

Вы можете предоставить еще больше информации, если преобразовать ошибку в NSError (что всегда возможно):

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

Приняв протокол CustomNSError, ошибка может предоставить словарь userInfo (а также domain и code). Пример:

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
person Martin R    schedule 27.08.2016
comment
Есть ли причина, по которой вы сначала делаете MyError Error, а потом расширяете его LocalizedError? Есть ли разница, если вы изначально сделали LocalizedError? - person Gee.E; 07.03.2017
comment
@ Gee.E: Нет никакой разницы. Это просто способ организовать код (одно расширение для каждого протокола). Сравните stackoverflow.com/questions/36263892/, stackoverflow.com/questions/40502086/ или natashatherobot.com/using-swift -расширения. - person Martin R; 07.03.2017
comment
Ах, проверьте. Я понимаю, о чем вы сейчас говорите. Раздел о соответствии протокола на natashatherobot.com/using-swift-extensions действительно является хороший пример того, что вы имеете в виду. Спасибо! - person Gee.E; 07.03.2017
comment
@MartinR Если моя ошибка будет преобразована в NSError, как я могу передать словарь с ошибкой, к которому можно получить доступ как userInfo NSError? - person BangOperator; 10.03.2017
comment
Остерегайтесь вводить var errorDescription: String? вместо String. Ошибка в реализации LocalizedError. См. SR-5858. - person ethanhuang13; 18.01.2018
comment
Разве вы все равно не получите localizedDescription, даже если не реализуете public var errorDescription: String?. Документы говорят, что у него есть реализация по умолчанию. Однако когда я делаю print(error.localizedDescription), я получаю сообщение об ошибке Операция не может быть завершена от компилятора. Делать print(error) нормально - person Honey; 14.09.2018
comment
Спасибо! Недокументированная магия всегда доставляет удовольствие, поэтому такие ответы огромная помощь. - person Raphael; 15.10.2018
comment
Как все это работает? Я имею в виду, что имя переменной, которое мы используем для регистрации, отличается от имени переменной, в которую мы фактически записали значение. Это должно быть вычисляемое свойство только для чтения, к которому мы обращаемся, верно? - person Honey; 29.12.2018
comment
@Honey: В Error ‹-› NSError bridging есть много «волшебства», сравните github.com/apple/swift-evolution/blob/master/proposals/ bridging.md. Реализация (я думаю) находится в github.com/ яблоко / swift-corelibs-foundation / blob / master /. - person Martin R; 29.12.2018
comment
Обновленная ссылка из комментария @ MartinR: github.com / яблоко / быстрая эволюция / blob / master / предложения / - person Theo; 11.11.2019
comment
@ ethanhuang13 Несмотря на то, что он зарегистрирован как ошибка, он не считается ошибкой. LocalizedError требует, чтобы тип errorDescription был String?, а не String. В противном случае будет предоставлена ​​реализация по умолчанию (операция не может быть завершена). С учетом сказанного, я ценю, что вы указали на это; Я сам совершал эту ошибку несколько раз. - person Peter Schorn; 27.08.2020
comment
@Honey Операция не может быть завершена ЯВЛЯЕТСЯ реализацией по умолчанию localizedDescription, если вы либо не реализуете errorDescription, либо возвращает nil. - person Peter Schorn; 27.08.2020

Я бы также добавил, если у вашей ошибки есть такие параметры

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

вы можете вызвать эти параметры в локализованном описании следующим образом:

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

Вы даже можете сделать это короче вот так:

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

Теперь существует два протокола принятия ошибок, которые может использовать ваш тип ошибки для предоставления дополнительной информации Objective-C - LocalizedError и CustomNSError. Вот пример ошибки, в которой используются оба варианта:

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

}
person matt    schedule 22.07.2017
comment
Вы можете отредактировать? Ваши примеры не очень помогают понять ценность каждого из них. Или просто удалите его, потому что ответ MartinR предлагает именно это ... - person Honey; 29.12.2018

Альтернативой может быть использование структуры. Немного элегантности со статической локализацией:

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)")
   }
}
person Zafer Sevik    schedule 26.01.2018

Это сработало для меня:

NSError(domain: "com.your", code: 0, userInfo: [NSLocalizedDescriptionKey: "Error description"])
person Rico Nguyen    schedule 26.08.2020

Вот более изящное решение:

  enum ApiError: String, LocalizedError {

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

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

  }
person Vitalii Gozhenko    schedule 29.07.2017
comment
Это может быть более элегантно во время выполнения, но на этапе статической локализации не удастся извлечь эти строки для переводчиков; вы увидите "Bad entry in file – Argument is not a literal string" ошибку, когда запустите exportLocalizations или genstrings для создания списка переводимого текста. - person savinola; 19.12.2017
comment
@savinola согласен, статическая локализация в таком случае работать не будет. Возможно, использование switch + case - единственный вариант ... - person Vitalii Gozhenko; 20.12.2017
comment
Использование необработанных значений также предотвратит использование связанных значений для любых ваших ошибок. - person Brody Robertson; 03.02.2019