2016-06-07 2 views
1

С добавлением ErrorType в Swift теперь можно выразить ошибки и ошибки в более чистом, более сжатом виде. Мы больше не связаны как разработчики iOS со старым способом NSError, неуклюжим и сложным в использовании.Swift - распространение ошибок при сохранении ясности api

ErrorType велика по нескольким причинам:

  • Общие параметры в функциях
  • Любое перечисление может соответствуют и реализовать его
  • работает лучше с улов/бросок парадигме

Однако есть некоторые проблемы и одна проблема, в частности, что я запутался в последнее время, и мне любопытно посмотреть, как другие решают эту проблему.

Скажите, например, вы создаете приложение для социальных сетей, похожее на Facebook, и у вас есть модель Group. Когда пользователь сначала загружает ваше приложение, вы хотите сделать две/три вещи:

  1. Извлечь соответствующие группы с вашего сервера.
  2. Получить уже сохраненные группы на диске (Realm, CoreData и т. Д.)
  3. Обновите локальную копию удаленной копией.

На протяжении всего этого процесса, вы можете разбить типы ошибок на две различные категории: 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, но опять же, я не вижу, как это сделать, не теряя информацию об ошибках на этом пути.

ответ

1

Просто попробуйте решение вашей проблемы, возможно, не идеально. Используя протокол, чтобы легко обойти объекты ошибки:

//1. 
protocol Error { 
    func errorDescription() 
} 

//2. 
enum PersistenceError: ErrorType, Error { 
    case CreateFailed([String: AnyObject) 
    case FetchFailed(NSPredicate?) 

    func errorDescription() { 

    } 
} 

//3. 
enum NetworkError: ErrorType, Error { 
    case GetFailed(AnyObject.Type) // where AnyObject is your Group model class 
    func errorDescription() { 

    } 
} 

//5. 
func fetch() -> SignalProducer<Group, Error> { 
    ... 
} 
+0

Это, безусловно, хорошее решение. К сожалению, он по-прежнему испытывает такую ​​же проблему, что любая функция или метод, возвращающий тип ошибки, всегда будет иметь тип «Ошибка». – barndog

+0

Правильно, но хотя тип - это ошибка, которая на самом деле является объектом подкласса с указателем на базовый указатель. И вы можете получить динамическое поведение на основе реализации типа подкласса метода протокола. Может быть, я не получаю контекст точно, но вы также можете улучшить поведение, добавив метод расширения в тип ErrorType и легко использовать его с любым типом пользовательского подкласса. – Tushar

Смежные вопросы