Лучшая практика для многопоточности в функции, которая возвращает значение, 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 2

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, но мы можем сделать это с помощью NSOperationQueues.

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
        }
    }