Как запланировать синхронную последовательность асинхронных вызовов в Combine? - PullRequest
0 голосов
/ 19 июня 2019

Я бы хотел обработать серию сетевых вызовов в моем приложении.Каждый вызов асинхронный, и flatMap() выглядит как правильный вызов.Однако flatMap обрабатывает все аргументы одновременно, и мне нужно, чтобы вызовы были последовательными - следующий сетевой вызов начинается только после завершения предыдущего.Я посмотрел на ответ RxSwift , но для этого требуется оператор concatMap, которого у Combine нет.Вот примерный план того, что я пытаюсь сделать, но flatMap запускает все myCalls одновременно.

Publishers.Sequence(sequence: urls)
  .flatMap { url in
    Publishers.Future<Result, Error> { callback in 
        myCall { data, error in 
            if let data = data {
                callback(.success(data))
            } else if let error = error {
                callback(.failure(error))
            }
        }
    }
  }

Ответы [ 2 ]

1 голос
/ 19 июня 2019

Вы также можете использовать prepend(_:) метод для наблюдаемого, который создает каскадную последовательность, которая, я полагаю, похожа на Observable.concat(:) в RxSwift .

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

func dataTaskPublisher(_ urlString: String) -> AnyPublisher<(data: Data, response: URLResponse), Never> {
    let interceptedError = (Data(), URLResponse())
    return Publishers.Just(URL(string: urlString)!)
                        .flatMap {
                            URLSession.shared
                                        .dataTaskPublisher(for: $0)
                                        .replaceError(with: interceptedError)
                        }
                        .eraseToAnyPublisher()
}

let publisher: AnyPublisher<(data: Data, response: URLResponse), Never> = Publishers.Empty().eraseToAnyPublisher()


for urlString in [
    "http://ipv4.download.thinkbroadband.com/1MB.zip",
    "http://ipv4.download.thinkbroadband.com/50MB.zip",
    "http://ipv4.download.thinkbroadband.com/10MB.zip"
    ] {
        publisher = publisher.prepend(dataTaskPublisher(urlString)).eraseToAnyPublisher()
}

publisher.sink(receiveCompletion: { completion in
    print("Completed")
}) { response in
    print("Data: \(response)")
}

Здесь оператор prepend(_:) ставит префикс последовательности, поэтому предварительно добавленные последовательности начинаются первыми, завершаются и начинаются после следующей последовательности.

Если вы запустите приведенный ниже код, вы должны увидеть, что сначала загружается 10 МБ файла, затем 50 МБ и, наконец, 1 МБ, так как сначала начинается последний добавленный файл, и так далее.

Существует другой вариант оператора prepend(_:), который принимает массив, но, похоже, не работает последовательно.

0 голосов
/ 19 июня 2019

После экспериментов на детской площадке, я думаю, я нашел решение, но если у вас есть идея получше, поделитесь.Решение состоит в том, чтобы добавить maxPublishers параметр к flatMap и установить значение max(1)

Publishers.Sequence(sequence: urls)
  .flatMap(maxPublishers: .max(1)) // <<<<--- here
  { url in 
    Publishers.Future<Result, Error> { callback in 
      myCall { data, error in 
        if let data = data {
          callback(.success(data))
        } else if let error = error {
          callback(.failure(error))
        }
      }
    }
  }
...