Swift error localizeddescription

Learn how to create, extend, throw, and handle custom error types in Swift.

Creating custom error types is an important skill when designing clear APIs for iOS and macOS applications. This post presents examples for creating, extending, throwing, and catching custom error types in Swift:

  1. Create A Custom Error And Conform To The Error Type
  2. Extend A Custom Error
  3. description for Custom Errors Using CustomStringConvertible
  4. localizedDescription For Custom Errors Using LocalizedError
  5. Throw Custom Errors
  6. Catch Custom Errors

Create A Custom Error And Conform To The Error Type

To create a custom error, create an enum in Swift that conforms to the Error protocol. Each case of the enum represents a unique error that can be thrown and handled:

enum CustomError: Error {
    // Throw when an invalid password is entered
    case invalidPassword

    // Throw when an expected resource is not found
    case notFound

    // Throw in all other cases
    case unexpected(code: Int)
}

Extend A Custom Error

Like all Swift types, new custom error types you create can be extended to add computed properties and functions. In this example, the isFatal computed property is added that can be used to determine if the error is recoverable:

extension CustomError {
    var isFatal: Bool {
        if case CustomError.unexpected = self { return true }
        else { return false }
    }
}

description for Custom Errors Using CustomStringConvertible

Custom errors implemented in Swift can have custom descriptions for each error. To add a description to a new error type, extend the custom error to conform to CustomStringConvertible and add a property description:

// For each error type return the appropriate description
extension CustomError: CustomStringConvertible {
    public var description: String {
        switch self {
        case .invalidPassword:
            return "The provided password is not valid."
        case .notFound:
            return "The specified item could not be found."
        case .unexpected(_):
            return "An unexpected error occurred."
        }
    }
}

localizedDescription For Custom Errors Using LocalizedError

New custom errors you create in Swift can also have localized custom descriptions for each error. To add a localized description to a new error type, extend the custom error to conform to LocalizedError and add a property errorDescription:

// For each error type return the appropriate localized description
extension CustomError: LocalizedError {
    public var errorDescription: String? {
        switch self {
        case .invalidPassword:
            return NSLocalizedString(
                "The provided password is not valid.", 
                comment: "Invalid Password"
            )
        case .notFound:
            return NSLocalizedString(
                "The specified item could not be found.", 
                comment: "Resource Not Found"
            )
        case .unexpected(_):
            return NSLocalizedString(
                "An unexpected error occurred.", 
                comment: "Unexpected Error"
            )
        }
    }
}

Throw Custom Errors

Functions marked as throws in Swift can throw custom errors directly:

func isAvailable(resourcePath: String) throws {
    if FileManager.default.fileExists(atPath: path) { 
        return true 
    }
    else {
        throw CustomError.notFound
    }
}

Catch Custom Errors

Custom errors can be individually caught and handled using the do catch syntax. Use catch followed by the specific error to catch the error and apply specific handling logic:

func open(resourcePath: String) {
    do {
        try isAvailable(resourcePath: resourcePath)
        // Handle opening the resource
    }
    catch CustomError.notFound {
        // Handle custom error
    }
    catch {
        // Handle other errors
    }
}

Create Your Own Errors In Swift

That’s it! By conforming to Error, CustomStringConvertible, and LocalizedError you can implement descriptive, clear, and actionable custom errors in Swift.

I was living under a rock until very recently and believed that the Error protocol was truly empty with no requirements. While it is true that there are no requirements, it does define a localizedDescription String property, as a quick pit stop at its documentation page shows.

This property, strangely, does not find mention in the Error handling section of Apple’s Swift book.

So, how does an error defined like

enum GraveError: Error {
  case learningSwift
}

supply a localizedDescription?

To understand this, I did some digging and found two great resources – the NSError bridging evolution proposal and the NSError.swift file in the open source Foundation library that is used on non-Apple platforms.

The relevant protocol extension is:

public extension Error {
  /// Retrieve the localized description for this error.
  var localizedDescription: String {
    if let nsError = self as? NSError {
      return nsError.localizedDescription
    }

    let defaultUserInfo = _swift_Foundation_getErrorDefaultUserInfo(self)  as? [String : Any]

    return NSError(domain: _domain, code: _code, userInfo: defaultUserInfo).localizedDescription
  }
}

Before continuing, allow me to take a small detour about the late great NSError. Behind the scenes, NSError also conforms to Error (it is empty after all), and always had a localizedDescription. This description is derived from what you put in an NSError’s userInfo dictionary, specifically the value for the NSLocalizedErrorDescriptionKey.

So, if the error is an NSError object, it just returns that object’s localizedDescription.
If it is not an NSError, and just a good old Swift Error, it creates a user info dictionary, then creates an NSError out of it, and then returns its localizedDescription.

This is where the LocalizedError protocol enters the stage.

The LocalizedError protocol inherits from the empty Error protocol and adds four requirements of its own (empty defaults are provided in extensions), one of which is the well named errorDescription. In the process of creating the afore-mentioned user info dictionary, this errorDescription is used for the NSLocalizedErrorDescriptionKey, if the error happens to be a LocalizedError.

There are more details in NSError.swift, which I leave for the interested reader.

The long and short is this: if you are in possession of any error (whether an NSError or Error), you can print its localizedDescription. For your NSErrors, populate the user info dictionary correctly and correspondingly, it does not hurt to get into the habit of conforming all your Swift Errors to LocalizedErrors and providing an errorDescription, like so:

extension GraveError: LocalizedError {
  var errorDescription: String? {
    switch self {
    case .learningSwift: return "Haha, good joke."
    }
  }
}

If not, you can end up with the (not so) dreaded “The operation couldn’t be completed __lldb_expr xxxx code x” message.

Good luck making mistakes!

Сколько раз мы всматривались в этот код:

do {
  try writeEverythingToDisk()
} catch let error {
  // ???
}

или в этот:

switch result {
case .failure(let error):
  // ???
}

и задавали себе вопрос: “Как же мне выудить из этой ошибки информацию?”

Проблема в том, что ошибка, вероятно, содержит много информации, которая могла бы нам помочь. Но получение этой информации часто дело не простое.

Чтобы понять причину этого, давайте рассмотрим имеющиеся в нашем распоряжении способы прикрепления информации к ошибкам.

Новое: LocalizedError

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

  • errorDescription
  • failureReason
  • recoverySuggestion

Соответствие протоколу LocalizedError вместо протокола Error (и обеспечение реализации этих новых свойств), позволяет нам дополнить нашу ошибку множеством полезной информации, которая может быть передана во время исполнения программы (интернет-журнал NSHipster рассматривает этот вопрос более подробно):

enum MyError: LocalizedError {
  case badReference

  var errorDescription: String? {
    switch self {
    case .badReference:
      return "The reference was bad."
    }
  }

  var failureReason: String? {
    switch self {
    case .badReference:
      return "Bad Reference"
    }
  }

  var recoverySuggestion: String? {
    switch self {
    case .badReference:
      return "Try using a good one."
    }
  }
}

Старое: userInfo

Хорошо знакомый класс NSError содержит свойство — словарь userInfo, который мы можем заполнить всем, чем захотим. Но также, этот словарь содержит несколько заранее определённых ключей:

  • NSLocalizedDescriptionKey
  • NSLocalizedFailureReasonErrorKey
  • NSLocalizedRecoverySuggestionErrorKey

Можно заметить, что их названия очень похожи на свойства LocalizedError. И, фактически, они играют аналогичную роль:

let info = [ 
  NSLocalizedDescriptionKey:
    "The reference was bad.",
  NSLocalizedFailureReasonErrorKey:
    "Bad Reference",
  NSLocalizedRecoverySuggestionErrorKey:
    "Try using a good one."
]

let badReferenceNSError = NSError(
  domain: "ReferenceDomain", 
  code: 42, 
  userInfo: info
)

Это выглядит, как будто LocalizedError и NSError должны быть в основном равнозначны, верно? Что ж, в этом-то и заключается основная проблема.

Старое встречается с новым

Дело в том, что класс NSError соответствует протоколу Error, но не протоколу LocalizedError. Иными словами:

badReferenceNSError is NSError        //> true
badReferenceNSError is Error          //> true
badReferenceNSError is LocalizedError //> false

Это значит, что если мы попытаемся извлечь информацию из любой произвольной ошибки привычным способом, то это сработает должным образом только для Error и LocalizedError, но для NSError будет отражено только значение свойства localizedDescription:

// The obvious way that doesn’t work:
func log(error: Error) {
  print(error.localizedDescription)
  if let localized = error as? LocalizedError {
    print(localized.failureReason)
    print(localized.recoverySuggestion)
  }
}

log(error: MyError.badReference)
//> The reference was bad.
//> Bad Reference
//> Try using a good one.

log(error: badReferenceNSError)
//> The reference was bad.

Это довольно неприятно, потому как известно, что наш объект класса NSError содержит в себе информацию о причине сбоя и предложение по исправлению ошибки, прописанные в его словаре userInfo. И это, по какой-то причине, не отображается через соответствие LocalizedError.

Новое становится старым

В этом месте мы можем впасть в отчаяние, мысленно представляя себе множество операторов switch, пытающихся отсортировать по типам и по наличию различные свойства словаря userInfo. Но не бойтесь! Есть несложное решение. Просто оно не совсем очевидно.

Обратите внимание, что в классе NSError определены удобные методы для извлечения локализованного описания, причины сбоя и предложения по восстановлению в свойстве userInfo:

badReferenceNSError.localizedDescription
//> "The reference was bad."

badReferenceNSError.localizedFailureReason
//> "Bad Reference"

badReferenceNSError.localizedRecoverySuggestion
//> "Try using a good one."

Они отлично подходят для обработки NSError, но не помогают нам извлечь эти значения из LocalizedError… или это так?

Оказывается, протокол языка Swift Error соединён компилятором с классом NSError. Это означает, что мы можем превратить Error в NSError с помощью простого приведения типа:

let bridgedError: NSError
bridgedError = MyError.badReference as NSError

Но ещё больше впечатляет то, что когда мы производим приведение LocalizedError этим способом, то мост срабатывает правильно и подключает localizedDescription, localizedFailureReason и localizedRecoverySuggestion, указывая на соответствующие значения!

Поэтому, если мы хотим, чтобы согласованный интерфейс извлекал локализованную информацию из Error, LocalizedError и NSError, нам просто нужно не долго думая привести всё к NSError:

func log(error: Error) {
  let bridge = error as NSError
  print(bridge.localizedDescription)
  print(bridge.localizedFailureReason)
  print(bridge.localizedRecoverySuggestion)
}

log(error: MyError.badReference)
//> The reference was bad.
//> Bad Reference
//> Try using a good one.

log(error: badReferenceNSError)
//> The reference was bad.
//> Bad Reference
//> Try using a good one.

Готово!

Оригинал статьи

As described in the Xcode 8 beta 6 release notes,

Swift-defined error types can provide localized error descriptions by adopting the new LocalizedError protocol.

In your case:

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.

You can provide even more information if the error is converted
to NSError (which is always possible):

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

By adopting the CustomNSError protocol the error can provide
a userInfo dictionary (and also a domain and code). Example:

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

Using a struct can be an alternative. A little bit elegance with static localization:

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

I would also add, if your error has parameters like this

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

you can call these parameters in your localized description like this:

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

You can even make this shorter like this:

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

There are now two Error-adopting protocols that your error type can adopt in order to provide additional information to Objective-C — LocalizedError and CustomNSError. Here’s an example error that adopts both of them:

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

}

# Error handling basics

Functions in Swift may return values, throw errors (opens new window), or both:

Any value which conforms to the ErrorType protocol (opens new window) (including NSError objects) can be thrown as an error. Enumerations (opens new window) provide a convenient way to define custom errors:

An error indicates a non-fatal failure during program execution, and is handled with the specialized control-flow constructs do/catch, throw, and try.

Errors can be caught with do/catch:

Any function which can throw an error must be called using try, try?, or try!:

# Catching different error types

Let’s create our own error type for this example.

The Do-Catch syntax allows to catch a thrown error, and automatically creates a constant named error available in the catch block:

You can also declare a variable yourself:

It’s also possible to chain different catch statements. This is convenient if several types of errors can be thrown in the Do block.

Here the Do-Catch will first attempt to cast the error as a CustomError, then as an NSError if the custom type was not matched.

In Swift 3, no need to explicitly downcast to NSError.

# Catch and Switch Pattern for Explicit Error Handling

In the client class:

# Disabling Error Propagation

The creators of Swift have put a lot of attention into making the language expressive and error handling is exactly that, expressive. If you try to invoke a function that can throw an error, the function call needs to be preceded by the try keyword. The try keyword isn’t magical. All it does, is make the developer aware of the throwing ability of the function.

For example, the following code uses a loadImage(atPath:) function, which loads the image resource at a given path or throws an error if the image can’t be loaded. In this case, because the image is shipped with the application, no error will be thrown at runtime, so it is appropriate to disable error propagation.

# Create custom Error with localized description

Create enum of custom errors

Create extension of RegistrationError to handle the Localized description.

Handle error:

For more information about errors, see The Swift Programming Language (opens new window).

Понравилась статья? Поделить с друзьями:
  • Swift error code z117
  • Swift decoding error
  • Swift catch error
  • Swi ошибка ивеко стралис
  • Swf2 read tool ошибка 10061