Протоколы ошибок
В Swift есть несколько протоколов по обработке ошибок.
Основной
- Error
Специализированные
- LocalizedError
- RecoverableError
- CustomNSError
Протокол ошибок (Protocol Error)
Тип данных, представляющий значение ошибки, которое может быть выброшено.
Это встроенный протокол, который объявлен как:
protocol Error: Sendable
Любой тип, декларирующий соответствие протоколу Error
, может использоваться для представления ошибки в системе обработки ошибок Swift. Поскольку у протокола Error
нет собственных требований, то можно заявить о соответствии для любого пользовательского типа, который создаете.
Использование перечислений в качестве ошибок
Перечисления Swift хорошо подходят для представления простых ошибок.
Для этого необходимо создать перечисление, которое соответствует протоколу Error
. В перечислении следует описать случаи для каждой возможной ошибки. Если есть дополнительные сведения об ошибке, которые могут быть полезны для решения проблемы с ошибкой, то следует использовать связанные значения (associated values), чтобы включить эту информацию.
В следующем примере показано перечисление IntParsingError
, которое фиксирует два разных типа ошибок, которые могут возникнуть при синтаксическом анализе целого числа из строки:
- переполнение, когда значение, представленное строкой, слишком велико для целочисленного типа данных,
- недопустимый ввод, когда нечисловые символы находятся на входе.
enum IntParsingError: Error {
case overflow
case invalidInput(Character)
}
В этом примере случай invalidInput
включает недопустимый символ в качестве связанного значения.
В следующем примере кода показано возможное расширение типа Int
, которое анализирует целочисленное значение экземпляра String
и выдает ошибку при возникновении проблемы во время анализа.
extension Int {
init(validating input: String) throws {
// ...
let c = _nextCharacter(from: input)
if !_isValid(c) {
throw IntParsingError.invalidInput(c)
}
// ...
}
}
При вызове новой инициализации Int
в операторе do
можно использовать сопоставление с шаблоном, чтобы сопоставить конкретные случаи пользовательского типа ошибки и получить доступ к их связанным значениям, как в примере ниже.
do {
let price = try Int(validating: "$100")
} catch IntParsingError.invalidInput(let invalid) {
print("Invalid character: '\(invalid)'")
} catch IntParsingError.overflow {
print("Overflow error")
} catch {
print("Other error")
}
// Output
// Invalid character: '$'
Включение дополнительных данных в ошибки
Иногда необходимо, чтобы разные состояния ошибок включали одни и те же общие данные, такие как позиция в файле или некоторые состояния вашего приложения. После того как вы это сделаете, используйте структуру для представления ошибок.
В следующем примере используется структура для представления ошибки при синтаксическом анализе XML-документа, включая номера строк и столбцов, в которых произошла ошибка:
do {
let price = try Int(validating: "$100")
} catch IntParsingError.invalidInput(let invalid) {
print("Invalid character: '\(invalid)'")
} catch IntParsingError.overflow {
print("Overflow error")
} catch {
print("Other error")
}
// Output
// Invalid character: '$'
Еще раз используйте сопоставление с образцом для условного отлова ошибок. Вот как вы можете перехватывать любые XMLParsingError
ошибки (синтаксического анализа XML), выдаваемые функцией parse(_:)
:
do {
let xmlDoc = try parse(myXMLData)
} catch let e as XMLParsingError {
print("Parsing error: \(e.kind) [\(e.line):\(e.column)]")
} catch {
print("Other error: \(error)")
}
// Output
// Parsing error: mismatchedTag [19:5]
В этом протоколе есть такое свойство localizedDescription
, которое позволяет получить локализованное описание ошибки.
Это свойство вычисляемое и объявлено как
var localizedDescription: String { get }
Специализированные протоколы ошибок
Есть несколько специализированных протоколов ошибок, которым перечисление с ошибками может соответствовать. И, вероятно, следует использовать их по сравнению с протоколом ошибок по умолчанию (Error), который предоставляется языком по умолчанию.
Все эти протоколы сами соответствуют протоколу Error
.
Ниже будут рассмотрено несколько протоколов, которые можно использовать при работе с ошибками в Swift.
LocalizedError
Протокол LocalizedError предоставляет четыре свойства для отображения пользователю информации об ошибках на его родном языке.
Ниже перечислены четыре свойства, и все они являются строками. Они необходимы, но они обеспечивают реализацию по умолчанию (Required. Default implementation provided.).
- var errorDescription: String?
- var failureReason: String?
- var helpAnchor: String?
- var recoverySuggestion: String?
Примером реализации может быть:
enum NetworkError: LocalizedError {
case noNetwork
case unexpectedResponse
var errorDescription: String {
switch self {
case .noNetwork:
NSLocalizedString("No network connection found", comment: "")
case .unexpectedResponse:
NSLocalizedString("The server returned an unexpected response", comment: "")
}
}
var failureReason: String? {
switch self {
case .noNetwork:
NSLocalizedString("Could not connect to the internet", comment: "")
case .unexpectedResponse:
NSLocalizedString("The server is not working properly", comment: "")
}
}
var recoverySuggestion: String? {
switch self {
case .noNetwork:
NSLocalizedString("Check your internet connection and try again", comment: "")
case .unexpectedResponse:
NSLocalizedString("Contact support", comment: "")
}
}
}
Есть еще одна интересная деталь об этом типе ошибок, а именно то, что при соединении с Objective-C
(или преобразовании в NSError
- casted as NSError
) все свойства протокола получают ключами userInfo
словаря NSError
:
NSLocalizedDescriptionKey
дляerrorDescription
NSLocalizedFailureReasonErrorKey
дляfailureReason
NSLocalizedRecoverySuggestionErrorKey
дляrecoverySuggestion
NSHelpAnchorErrorKey
дляhelpAnchor
Так что, если необходимо, чтобы ваши ошибки были связаны с Objective-C, это одна из специализаций, которую следует учитывать.
RecoverableError
Протокол RecoverableError предоставляет средства, которые помогут вашим пользователям попытаться восстановиться после ошибок. Эта специализация предоставляет одно свойство и два метода:
- var recoveryOptions: [String]: это массив строк, которые можно показать своему пользователю при попытке восстановления после ошибок. Это свойство является обязательным и не предоставляется реализация по умолчанию (Required);
- func attemptRecovery(optionIndex: Int) -> Bool: используйте это, чтобы попытаться восстановиться после ошибки, а затем верните логическое значение, указывающее, была ли операция успешной или нет.
optionIndex
соответствует индексу опции в массивеrecoveryOptions
. Этот метод обязательный и не предоставляется реализация по умолчанию (Required); - func attemptRecovery(optionIndex: Int, resultHandler: (Bool) -> Void): как и в предыдущем методе, используйте этот метод, чтобы попытаться исправить ошибку. Разница в том, что используется замыкание для передачи результата восстановления, поэтому вы можете использовать его, когда необходимо попытаться восстановить используя асинхронные операции. Этот метод обязательный и предоставляется реализация по умолчанию (Required. Default implementation provided).
Быстрый пример реализации:
enum NetworkError: RecoverableError {
case noNetwork
case unexpectedResponse
var recoveryOptions: [String] {
switch self {
case .noNetwork:
return [
NSLocalizedString("Retry", comment: ""),
NSLocalizedString("Open Settings to Change Network", comment: "")
]
case .unexpectedResponse:
return [
NSLocalizedString("E-Mail support", comment: ""),
NSLocalizedString("Change server", comment: "")
]
}
}
func attemptRecovery(optionIndex recoveryOptionIndex: Int) -> Bool {
switch self {
case .unexpectedResponse:
if recoveryOptionIndex == 0 {
// Mail support
} else if recoveryOptionIndex == 1 {
// Change server
}
return true
default:
return false
}
}
// ...
}
Опять же, к свойствам можно получить доступ через userInfo
NSError
, используя NSLocalizedRecoveryOptionsErrorKey
для recoveryOptions
и ключ NSRecoveryAttempterErrorKey
для доступа к параметрам восстановления.
CustomNSError
Наконец, протокол CustomNSError
предоставляет свойства для создания хорошо известного объекта NSError
с доменом ошибки , кодом ошибки и информацией о пользователе. Все свойства являются обязательными, но предоставляется реализация по умолчанию для каждого (Required. Default implementation provided.):
- static var errorDomain: String: это домен ошибки в обратной нотации DNS.
- var errorCode: Int: код ошибки в виде
Int
. - var errorUserInfo: [String : Any]: в виде словаря [String: Any].
Быстрый пример реализации:
enum NetworkError: CustomNSError {
enum ErrorCode: Int {
case noNetwork
case unexpectedResponse
}
var errorDomain = "com.andyibanez.com.myApp.NetworkError"
var appErrorCode: ErrorCode
var errorCode: Int {
return self.appErrorCode.rawValue
}
var errorUserInfo: [String : Any] {
let dic = [
"URL": //...,
]
}
}
Поскольку вы сами предоставляете userInfo
, то вам не нужно беспокоиться о том, какие у него ключи. Он обладает большой гибкостью, но с ним сложнее работать.
Смешивание несколько протоколов вместе
Помните, что это протоколы, и вам разрешено соответствовать более чем одному протоколу одновременно. Поэтому ничто не мешает вам, скажем, создать исправимую локализованную ошибку.
Пример:
enum NetworkError: LocalizedError, RecoverableError {
case noNetwork
case unexpectedResponse
// MARK: - LocalizedError
var localizedDescription: String {
switch self {
case .noNetwork:
return NSLocalizedString("No network connection found", comment: "")
case .unexpectedResponse:
return NSLocalizedString("The server returned an unexpected response", comment: "")
}
}
var failureReason: String? {
switch self {
case .noNetwork:
return NSLocalizedString("Could not connect to the internet", comment: "")
case .unexpectedResponse:
return NSLocalizedString("The server is not working properly", comment: "")
}
}
// MARK: - RecoverableError
var recoverySuggestion: String? {
switch self {
case .noNetwork:
return NSLocalizedString("Check your internet connection and try again", comment: "")
case .unexpectedResponse:
return NSLocalizedString("Contact support", comment: "")
}
}
var recoveryOptions: [String] {
switch self {
case .noNetwork:
return [
NSLocalizedString("Retry", comment: ""),
NSLocalizedString("Open Settings to Change Network", comment: "")
]
case .unexpectedResponse:
return [
NSLocalizedString("E-Mail support", comment: ""),
NSLocalizedString("Change server", comment: "")
]
}
}
func attemptRecovery(optionIndex recoveryOptionIndex: Int) -> Bool {
switch self {
case .unexpectedResponse:
if recoveryOptionIndex == 0 {
// Mail support
} else if recoveryOptionIndex == 1 {
// Change server
}
return true
default:
return false
}
}
}
Вывод
Обработка ошибок в Swift становится проще, когда использовать эти протоколы ошибок. Они могут сильно помочь в написании лучшего кода обработки ошибок, который работает во всей платформе Foundation и в остальных API.
Еще полезные ссылки
Также информацию по протоколам ошибок можно получить на странице официальной документации.
Ссылки на официальную документацию: