С добавлением ErrorType
в Swift теперь можно выразить ошибки и ошибки в более чистом, более сжатом виде. Мы больше не связаны как разработчики iOS со старым способом NSError
, неуклюжим и сложным в использовании.Swift - распространение ошибок при сохранении ясности api
ErrorType
велика по нескольким причинам:
- Общие параметры в функциях
- Любое перечисление может соответствуют и реализовать его
- работает лучше с улов/бросок парадигме
Однако есть некоторые проблемы и одна проблема, в частности, что я запутался в последнее время, и мне любопытно посмотреть, как другие решают эту проблему.
Скажите, например, вы создаете приложение для социальных сетей, похожее на Facebook, и у вас есть модель Group
. Когда пользователь сначала загружает ваше приложение, вы хотите сделать две/три вещи:
- Извлечь соответствующие группы с вашего сервера.
- Получить уже сохраненные группы на диске (Realm, CoreData и т. Д.)
- Обновите локальную копию удаленной копией.
На протяжении всего этого процесса, вы можете разбить типы ошибок на две различные категории: PersistenceError
и NetworkError
где ErrorType
соответствующие перечислений может выглядеть
enum PersistenceError: ErrorType {
case CreateFailed([String: AnyObject)
case FetchFailed(NSPredicate?)
}
enum NetworkError: ErrorType {
case GetFailed(AnyObject.Type) // where AnyObject is your Group model class
}
Есть несколько способов/шаблоны проектирования вы можете использовать для доставки ошибок. Самым распространенным, конечно же, является try/catch.
func someFunc() throws {
throw .GetFailed(Group.self)
}
Здесь, поскольку функции, которые бросают еще не может указать, какой тип ошибки они бросали, хотя я подозреваю, что изменится, вы можете легко бросить NetworkError
или PersistenceError
.
Проблема возникает при использовании более общего или функционального подхода, такого как ReactiveCocoa или Result.
Затем обертывание два вызова:
func fetch() -> SignalProducer<Group, ???> {
let remoteProducer = self.fetchGroupsFromRemote()
.flatMap(.Concat)) { self.saveGroupsToLocal($0) // returns SignalProducer<[Group], PersistenceError> }
let localProducer = self.fetchGroupsFromLocal()
return SignalProducer(values: [localProducer, remoteProducer]).flatten(.Merge)
}
Какой тип ошибки идет в том месте, отмеченные ???
? Это NetworkError
или PersistenceError
? Вы не можете использовать ErrorType
, потому что он не может использоваться как конкретный тип, и если вы попытаетесь использовать его в качестве общего ограничения, то <E: ErrorType>
, компилятор все равно будет жаловаться на то, что он ожидает список аргументов типа E
.
Таким образом, проблема становится, тем более с попыткой/уловкой, и тем более с функциональными подходами, как поддерживать структуру иерархии ошибок, чтобы информация об ошибках сохранялась в разных форматах ErrorType
, сохраняя при этом описательную ошибку api.
Лучшее, что я могу придумать до сих пор:
enum Error: ErrorType {
// Network Errors
case .GetFailed
// Persistence Errors
case .FetchFailed
// More error types
}
, который, по существу, один длинный перечисление ошибок, так что любые ошибки одного и того же типа, и даже самая глубокая ошибка может распространяться вверх по цепь.
Как другие люди имеют дело с этим? Мне нравятся преимущества наличия единого универсального перечня ошибок, но читаемость и api clearify страдают. Я бы предпочел, чтобы каждая функция описывала, какой конкретный кластер ошибок они возвращают, а не каждый возвращаемый Error
, но опять же, я не вижу, как это сделать, не теряя информацию об ошибках на этом пути.
Это, безусловно, хорошее решение. К сожалению, он по-прежнему испытывает такую же проблему, что любая функция или метод, возвращающий тип ошибки, всегда будет иметь тип «Ошибка». – barndog
Правильно, но хотя тип - это ошибка, которая на самом деле является объектом подкласса с указателем на базовый указатель. И вы можете получить динамическое поведение на основе реализации типа подкласса метода протокола. Может быть, я не получаю контекст точно, но вы также можете улучшить поведение, добавив метод расширения в тип ErrorType и легко использовать его с любым типом пользовательского подкласса. – Tushar