использование группы отправки в цикле multi for с задачами URL-сессии

Я использую группу отправки wait(), которая блокирует мой цикл for от завершения кода до тех пор, пока набор задач urlsession (в другом цикле с обработчиком завершения) не будет завершен перед добавлением нового элемента в мой массив

текущий код завершит первый цикл до завершения второго цикла urlClass.selectfoodURL

Я хочу добавить массив в историю еды после завершения цикла urlfood for из-за того, что проблема в моем подходе к использованию групп отправки - это wait(), когда моя выбранная еда называется urlsession застрял и не завершается с group.wait

func userSnackHistoryArray() {
    let group = DispatchGroup()
    let Arrays // array of dictionary
    for array in Arrays {

        var generateMeal = MealDetails() // struct type
        do {
            let aa = try JSONDecoder().decode(userSnack.self, from: array)
            generateMeal.names = convertToJsonFile.type

            for name in generateMeal.names!{
                group.enter()

                urlClass.selectfoodURL(foodName: name){ success in
                    generateMeal.units!.append(allVariables.selectedUnit)
                    group.leave()
                }
            }
            // my select food is called but the urlsession stuck and doesnt complete with group.wait is active
            // group.wait()
            mealHistory.append(generateMeal)
        } catch { }
    }

    group.notify(queue: .main){
        print("complete")
    }
}

Я сократил свой код, чтобы сосредоточиться на проблеме. Я могу разделить свой код на две функции и решить проблему, но я хочу использовать только одну функцию. Есть предложения или идеи?


person Mohammad Abusalha    schedule 10.10.2019    source источник


Ответы (1)


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

func retrieveSnacks() {
    var snacksToAdd: [Snack] = []

    let group = DispatchGroup()

    ...

    for url in urls {
        group.enter()
        fetchSnack(with: url) { result in
            dispatchPrecondition(condition: .onQueue(.main))   // note, I’m assuming that this closure is running on the main queue; if not, dispatch this appending of snacks (and `leave` call) to the main queue

            if case .success(let snack) = result {
                snacksToAdd.append(snack)
            }

            group.leave()
        }
    }

    // when all the `leave` calls are called, only then append the results

    group.notify(queue: .main) {
        self.snacks += snacksToAdd

        // trigger UI update, or whatever, here
    }
}

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

func retrieveSnacks() {
    var snacksToAdd: [URL: Snack] = [:]

    let group = DispatchGroup()

    ...

    for url in urls {
        group.enter()
        fetchSnack(with: url) { result in
            if case .success(let snack) = result {
                snacksToAdd[url] = snack
            }
            group.leave()
        }
    }

    group.notify(queue: .main) {
        let sortedSnacks = urls.compactMap { snacksToAdd[$0] }
        self.snacks += sortedSnacks

        // trigger UI update, or whatever, here
    }
}

Наконец, я мог бы предложить принять шаблон обработчика завершения:

func retrieveSnacks(completion: @escaping ([Snack]) -> Void) {
    var snacksToAdd: [URL: Snack] = [:]

    let group = DispatchGroup()

    ...

    for url in urls {
        group.enter()
        fetchSnack(with: url) { result in
            if case .success(let snack) = result {
                snacksToAdd[url] = snack
            }
            group.leave()
        }
    }

    group.notify(queue: .main) {
        let sortedSnacks = urls.compactMap { snacksToAdd[$0] }
        completion(sortedSnacks)
    }
}

retrieveSnacks { addedSnacks in
    self.snacks += addedSnacks
    // update UI here
}

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


Я прошу прощения за то, что вышеприведенное несколько изменено из вашего фрагмента кода, но мне этого было недостаточно, чтобы проиллюстрировать, как именно это будет выглядеть. Но, надеюсь, приведенное выше иллюстрирует шаблон, и вы можете увидеть, как вы можете применить его к своей кодовой базе. Итак, не теряйтесь в деталях, а сосредоточьтесь на основном шаблоне создания записей, которые будут добавляться в локальную переменную, и обновляйте только окончательные результаты в блоке .notify.

FWIW, это сигнатура метода, который приведенные выше фрагменты используют для асинхронной выборки рассматриваемых объектов.

func fetchSnack(with url: URL, completion: @escaping (Result<Snack, Error>) -> Void) {
    ...

    // if async fetch not successful 

    DispatchQueue.main.async {
        completion(.failure(error))
    }

    // if successful

    DispatchQueue.main.async {
        completion(.success(snack))
    }
}
person Rob    schedule 31.10.2019
comment
Благодарю вас! Это было настоящим спасением; Я изо всех сил пытался правильно настроить вызовы DispatchGroup, и это здорово. - person Graystripe; 04.08.2021