Лучшая практика для многопоточности в функции, которая возвращает значение, Swift
У меня есть вопрос, который может быть не конкретно о реализации, а скорее совет/лучшая практика.
Я работаю над классом в Swift, который получает данные из онлайн-источника в формате JSON. Я хочу, чтобы в этом классе были определенные методы, которые подключаются к онлайн-источнику и возвращают результат в типе словаря. Функция может быть такой:
func getListFromOnline()->[String:String]{
var resultList : [String:String]=[:]
...
/*
Some HTTP request is sent to the online source with NSURLRequest
Then I parse the result and assign it to the resultList
*/
...
return resultList
}
То, что я получаю прямо сейчас в этой реализации, без многопоточности, resultList
, по-видимому, возвращается перед выборкой из онлайн-источника выполняется. Неудивительно, что это приводит к сбою в программе.
Есть идеи или советы, как я могу достичь противоположного? Я немного запутался с многопоточностью в этом случае, потому что я хочу вызвать этот открытый метод асинхронно позже из другого класса, и я знаю, как это сделать, но я не знаю, как сделать так, чтобы метод, который возвращает аргумент, имел многопоточность внутри себя.
Или дело вовсе не в многопоточности и я не вижу здесь очевидного решения?
3 ответа:
Существует три простых и полностью ожидаемых подхода к этой проблеме в разработке Swift/Objective-C, и ни одно из этих решений не включает метод, возвращающий значение напрямую. Вы могли бы написать код, который ждал завершения асинхронной части (блокирование потока), а затем возвращал бы значение, и есть случаи, когда это делается в некоторых собственных библиотеках Apple, но я не собираюсь описывать подход, потому что это действительно не так уж здорово. идея.
Первый подход включает в себя блоки завершения. Когда наш метод собирается выполнить некоторый асинхронный код, мы можем передать блок кода для выполнения всякий раз, когда асинхронная работа выполнена. Такой метод будет выглядеть следующим образом:func asynchStuff(completionHandler: ([String:String]) -> Void) { // do asynchronous stuff, building a [String:String] let result: [String: String] = // the result we got async completionHandler(result) }
Не забудьте вызвать
completionHandler()
в том же потоке, который был вызванasynchStuff
. (Этот пример не демонстрирует этого.)
Второй подход включает делегатов и протоколы.Нам нужен класс, чтобы выполняйте асинхронную работу. Этот класс будет содержать ссылку на наш делегат, который будет реализовывать методы завершения. Во-первых, протокол:
@objc protocol AsyncDelegate { func complete(result: [String:String] }
Теперь наш асинхронный рабочий:
class AsyncWorker { weak var delegate: AsyncDelegate? func doAsyncWork() { // like before, do async work... let result: [String: String] = // the result we got async self.delegate?.complete(result) } }
Не забудьте убедиться, что мы вызываем метод делегата завершения в том же потоке, в котором был вызван
doAsyncWork()
. (Этот пример не демонстрирует этого.)
Третий подход может быть реализован с помощьюNSNotificationCenter
. Времена, когда это является подходящим подходом, будут очень редкими что я даже не собираюсь утруждать себя даже элементарным примером, как я сделал два других примера, потому что вы почти наверняка должны использовать один из первых двух примеров почти в каждом сценарии.
Какой подход вы используете, полностью зависит от вашего конкретного варианта использования. В Objective-C я часто предпочитал делегирование блочному подходу (хотя иногда блоки корректны), но Swift позволяет нам передавать регулярные функции / методы в качестве аргумента блока, поэтому он склоняет меня немного назад к использованию блочного подхода для Swift, но все же используйте правильный инструмент для правильной работы, как всегда.
Я хотел расширить этот ответ, чтобы обратиться к вызову обратного вызова (будь то блоки или делегаты) в соответствующем потоке. Я не уверен, что есть способ сделать это с помощью GCD, но мы можем сделать это с помощью
NSOperationQueue
s.func asyncStuff(completionHandler: ([String:String]) -> Void) { let currentQueue = NSOperationQueue.currentQueue() someObject.doItsAsyncStuff(someArg, completion: { result: [String:String] in currentQueue.addOperationWithBlock() { completionHandler(result) } } }
Краткий ответ:Вы не можете. это не то, как работает асинхронная обработка. Результат никогда не будет доступен в момент возврата метода. На самом деле ваш метод возвращает до асинхронной обработки даже сущностей.
Как говорит Абакерсмит в своем комментарии, то, что вы должны сделать, это добавить закрытие обработчика завершения (он же блок) в качестве параметра функции. Это блок кода, который передает вызывающий абонент. Вы пишете свой метод так, чтобы после загрузки асинхронных данных JSON он вызывал блок завершения.
Затем в вызывающем устройстве вы пишете код для передачи кода, который вы хотите выполнить после завершения загрузки в блоке завершения. Вы должны структурировать свою программу так, чтобы она могла работать без ваших данных (например, отображая пустое табличное представление) и обновлять себя после поступления данных. Вы можете написать свой блок завершения, чтобы установить данные в модель данных для табличного представления, а затем вызвать reloadData в табличном представлении.
Обычно безопаснее, если вы пишете свой асинхронный метод, чтобы он выполнял блок завершения в главном потоке.
Вот пример того, как выполнить асинхронную работу в отдельном потоке и затем вернуться обратно в основной поток, чтобы обновить пользовательский интерфейс
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT dispatch_async(dispatch_get_global_queue(priority, 0)) { // do some asynchronous work dispatch_async(dispatch_get_main_queue()) { // use returned data to update some UI on the main thread } }