Предположим такую ​​простую структуру:

struct Foo {
    let value: Int
    let delay: TimeInterval
}

Он имеет 2 функции, каждая из которых принимает закрытие в качестве параметра. Один вызывается синхронно, другой - асинхронно после delay:

extension Foo {
    func sync(_ completion: (Int) -> Void) {
        completion(value)
    }
}

extension Foo {
    func async(_ completion: @escaping (Int) -> Void) {
        DispatchQueue.main.asyncAfter(deadline: .now() + TimeInterval(delay)) {
            completion(self.value)
        }
    }
}

Теперь предположим, что у меня есть массив объектов Foo:

let foo1 = Foo(value: 1, delay: 1)
let foo2 = Foo(value: 2, delay: 0)

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

let syncValues = [foo1, foo2].reduce(into: []) { values, foo in foo.sync { values.append($0) } } // [1, 2]

Я хочу сделать это асинхронно и получить отчет foo2 до того, как foo1 сделает:

let asyncValues = [foo1, foo2].reduce(into: []) { values, foo in foo.async { values.append($0) }  // [2, 1]}

Однако при попытке выполнить асинхронную версию я получаю сообщение об ошибке: Escaping closure captures 'inout' parameter 'values'

Как лучше всего выполнять эту асинхронную задачу?

0
Tim Fuqua 9 Дек 2020 в 02:49

1 ответ

Лучший ответ

Поскольку это асинхронно, вы не можете сделать это в одном синхронном операторе. Предполагая, что вы понимаете это, вероятно, проще всего использовать Combine:

import Combine

let fooResultsPublishers = [foo1, foo2].publisher
    .flatMap { foo in
        Future { promise in
           foo.async { promise(.success($0)) }
        }
    }
    .collect()

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

fooResultsPublishers
    .sink {
       print($0) // [2,1]
    }
    // store it in long-living property, like
    // var cancellables: Set<AnyCancellable> = []
    .store(in: &cancellables) 

Без Combine вам нужно было бы использовать что-то вроде DispatchGroup:

let group = DispatchGroup()
var values: [Int] = []
[foo1, foo2].forEach { foo in
    group.enter()
    foo.async { values.append($0); group.leave() }
}

group.notify(queue: .main) {
    print(values) // [2,1]
}

3
New Dev 9 Дек 2020 в 00:51