2015-12-23 4 views
4

Я создаю структуру для веб-сервисов, используемых в моем проекте. Я загрузил шаблон в GitHub. https://github.com/vivinjeganathan/ErrorHandlingОбработка ошибок - Async Call

Он имеет различные слои. Уровень 1 для проверки. Уровень 2 для формирования запроса. Уровень 3 для фактического сетевого вызова.

< Посмотреть контроллер ----> Слой 1 < ---> Слой 2 < ---> Слой течет 3

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

Я ссылался на эту ссылку для обработки ошибок в асинхронных вызовах - http://appventure.me/2015/06/19/swift-try-catch-asynchronous-closures/ Создал ветку в том же репо - имя - ErrorHandling-Method1.

Я смог передать ошибку с уровня 3 на уровень 2 (Single Level - возврат ответа через функции в замыканиях - как упоминалось в ссылке). Но сталкиваются с трудностями при передаче обратно через несколько слоев.

Может ли кто-нибудь помочь с образцом приложения, представленным публично GitHub?

ответ

3

Прежде всего, я не думаю, что необходимо складывать слои так, как вы делали, например, добавляя функциональность проверки в качестве слоя, с которым вы увеличиваете связь, создавая слой, зависящий от нижележащих слоев (разбор, сети и т.д.), вместо этого, почему бы не разделить проверки, чтобы сделать это только зависит от данных ?:

class ViewController: UIViewController { 

    var validator = InputValidator() 

    override func viewDidLoad() { 
     super.viewDidLoad() 

     do { 
     try validator.validateInput("INPUT") 
     try Fetcher.requestDataWithParams("INPUT") 
     } 
     catch { 
     handleError(error) 
     }   
    } 
} 

Теперь функциональность проверки не зависит от других слоев, поэтому связь будет течь, как это :

View Controller < ---> ParsingLayer < ---> NetworkingL ayer

Я переименовал слои, но они не обязательно должны быть такими, вы можете добавлять или удалять слои.

Я думаю, что это будет своего рода сложными, если я попытаюсь объяснить свой подход, поэтому я собираюсь привести пример с использованием предыдущих слоев, сначала нижний слой:

class NetworkingLayer { 
    class func requestData(params: AnyObject, completion: (getResult:() throw -> AnyObject) -> Void) -> Void { 
     session.dataTaskWithURL(url) { (data, urlResponse, var error) in 
     if let error = error { 
      completion(getResult: { throw error }) 
     } else { 
      completion(getResult: { return data }) 
     } 
     } 
    } 
} 

Я опустил некоторые разделы кода, но идея состоит в том, чтобы сделать любой необходимый шаг, чтобы заставить слой работать (создать сеанс и т. д.) и всегда поддерживать связь через завершение закрытия; слой сверху будет выглядеть следующим образом:

class ParsingLayer { 
    class func requestObject(params: AnyObject, completion: (getObject:() throw -> CustomObject) -> Void) -> Void { 
     NetworkingLayer.requestData(params, completion: { (getResult) -> Void in 
     do { 
      let data = try getResult() 
      let object = try self.parseData(data) 
      completion(getObject: { return object }) 
     } 
     catch { 
      completion(getObject: { throw error }) 
     } 
     }) 
    } 
} 

Обратите внимание, что закрытие завершающего не то же самое, так как каждый слой добавляет функциональность, возвращаемый объект может меняться, также заметить, что код внутри do заявления может потерпеть неудачу в двумя способами, сначала, если сетевой вызов завершается с ошибкой, а затем, если данные из сетевого уровня не могут быть проанализированы; снова соединение с верхним слоем всегда выполняется через завершение закрытия.

Наконец ViewController может вызвать следующий слой с помощью закрытия ожидаемого разборе слоя в этом случае и могут обрабатывать ошибки возникли в любом слое:

override func viewDidLoad() { 
    super.viewDidLoad() 
    do { 
     try validator.validateInput("INPUT") 
     try ParsingLayer.requestObject("INPUT", completion: { (getObject) in 
     do { 
     let object = try getObject() 
     try self.validator.validateOutput(object) 
     print(object) 
     } 
     catch { 
     self.handleError(error) 
     } 
    }) 
    catch { 
     handleError(error) 
    }   
} 

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

Надеюсь, это поможет.

+0

Спасибо за подробное объяснение. Мне понравился ваш подход к устранению более позднего для проверки. Но место, пусть data = try getResult() [В PARSING LAYER] & let object = try getObject() [In View Controller] не очень удобно. Лично я чувствую, что NSNotification чище и проще. Можете ли вы сказать, есть ли у вас какие-либо преимущества вашего подхода к уведомлениям? Потому что я не нахожу. Я был бы рад узнать об этом и отредактировать свой код соответствующим образом. – vivin

+0

В этом случае уровень анализа зависит от сетевого уровня, связь имеет отношение 1 к 1, нет необходимости использовать систему вещания, некоторые из недостатков использования уведомлений: - не компилировать время для проверки на чтобы уведомления были правильно обработаны наблюдателями. -Не очень прослеживается (трудно отлаживать) - Добавляет еще один уровень сложности -Notification Names, а ключи словаря UserInfo должны быть известны как наблюдателям, так и контроллерам. Поскольку каждый может подписаться на уведомление, вероятно, существует плохая инкапсуляция данных – juanjo

+0

Я должен был сказать, что я использовал подход в своем ответе в нескольких приложениях, у меня есть сетевой уровень, который обменивается данными с уровнем синтаксического анализа, который передает обратно на уровень репозитория, который наконец используется уровнем представления (View Controllers) и уровня обслуживания; сетевой уровень обрабатывает только связь с сервером, слой Parsing преобразует ответ JSON в объекты модели с помощью интроспекции и генериков, уровень репозитория обрабатывает URL-адреса и параметры, а все данные и ошибки протекают через цепочку без каких-либо проблем без уведомлений. – juanjo

2

Зачем объявлять ваш метод бросками, если вы никогда не бросаете или даже не пытаетесь поймать? Вы можете выкидывать ошибки с помощью объявляемого объявления через все слои и даже изменять тип throwable на каждом уровне.

ОБНОВЛЕНИЕ: Не думал бросить не работать в асинхронных операциях. Использование NSNotification - один хороший маршрут, или вы можете взглянуть на RXSwift или аналогично решить его. Моя личная рекомендация - использовать RxSwift. Это избавляет вас от обратного ада, с которым вы в настоящее время путешествуете.

+0

Это невозможно, если ваши замыкания асинхронны;) – CouchDeveloper

3

Лично я использовал бы уведомления, передающие NSError как объект уведомления в слоях и наблюдая уведомление в контроллере представления.

В слоях:

NSNotificationCenter.defaultCenter().postNotificationName("ErrorEncounteredNotification", object: error) 

В контроллере представления

NSNotificationCenter.defaultCenter().addObserver(self, selector: "errorEncountered:", name: "ErrorEncounteredNotification", object: nil) 

способ выбора:

func errorEncountered(notification: NSNotification!) { 
    let error: NSError! = notification.object as! NSError 
    NSLog("error: \(error)") 
} 
+0

Зачем вам размещать уведомление, а не ловить ошибку, получившуюся в любом случае? – tskulbru

2

Вы правильно определили неприятные проблемы с обработкой ошибок в асинхронном коде.

Кажется, что с синхронными функциями - это просто возврат кода ошибки или дополнительный параметр ошибки или использование нового синтаксиса Swift throws. Вот синхронное функция:

func computeSome() throws -> Some 

И это жизнеспособный функция подпись для функции асинхронного:

func computeSomeAsync(completion: (Some?, NSError?) ->()) 

Асинхронная функция возвращает Void и не бросает. Если он терпит неудачу, он вызывает функцию завершения с набором параметров ошибки.

Однако обработчики завершения становятся очень громоздкими, особенно во вложенном коде.

Решение использовать будущее:

func computeSomeAsync() -> Future<Some> 

Эта функция является асинхронным и не бросает - и возвращает будущее. Итак, что такое будущее?

Ну, будущее представляет собой Возможные последствия: результат асинхронной функции. Когда вы вызываете асинхронную функцию, она немедленно возвращается, и вы получаете placeholder. Это, названное будущим, будет в конечном итоге завершено базовой задачей фона, которая вычисляет значение.

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

В зависимости от фактической реализации и API библиотеки будущего, вы можете получить результат путем регистрации продолжений:

let future = computeSomeAsync() 

future.onSuccess { value in 
    print("Value: \(value)") 
} 


future.onFailure { error in 
    print("Error: \(error)") 
} 

Это может показаться странным на первый, но вы можете сделать удивительные вещи с фьючерсами :

fetchUser(id).flatMap { user in 
    fetchProfileImage(user.profileImageUrl).flatMap { image in 
     cacheImage(image) 
    } 
} 
.onFailure { error in 
    print("Something went wrong: \(error)") 
} 

Данное заявление является асинхронным - а также функции fetchUser, fetchProfileImage и cacheImage. Включена обработка ошибок.

+0

Спасибо. Но я не полностью понимаю ваш подход (будущая функция и ее преимущества). Если вы не возражаете, можете просто изменить свой код в github, чтобы я мог играть с ним. – vivin

+0

@vivin Вы можете клонировать [FutureLib] (https://github.com/couchdeveloper/FutureLib) и читать файл README. Откройте проект в Xcode и создайте новый файл игровых площадок и попробуйте воспроизвести и понять простые образцы на игровой площадке. Взгляните на существующую игровую площадку, чтобы получить образцы функций, которые имитируют сетевые запросы. Возможно, посмотрите также на [BrightFutures] (https://github.com/Thomvis/BrightFutures), что является еще одной довольно удивительной реализацией фьючерсов и обещаний в стиле Скала. – CouchDeveloper