Как предоставить локализованное описание с типом ошибки в 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 здесь имеет тип Error, а не MyError. Я могу, конечно, бросить предмет в MyError

(error as? MyError)?.localizedDescription

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

5 ответов


как описано в примечаниях к выпуску 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

Я бы также добавил, Если ваша ошибка имеет такие параметры, как это

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

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

extension NetworkError {
  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 {
  var errorDescription: String {
    switch self {
    case let .responseStatusError(status, message):
      return "Error with status \(status) and message \(message) was thrown"
  }
}

теперь есть два протокола принятия ошибок, которые ваш тип ошибки может принять, чтобы предоставить дополнительную информацию 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" }

}

вот более элегантное решение:

  enum ApiError: String, LocalizedError {

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

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

  }

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

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