I want migrate to combine and now trying to understand how to do this. By learning i faced with one problem, that i don't understand how better to do.
I have dynamic lists with "AnyPublisher".
I will describe what i have for better understanding what i want.
I have methods for making requests, methods returns AnyPublisher
with type
and APIError
.
/// return standarts
func fetchStandarts(by key: String) -> AnyPublisher<[A11yStandart], APIError>
/// return accessibilities
func fetchAccessibilities(by id: String) -> AnyPublisher<AccessibilitiesResult, APIError>
/// return images
func fetchImages(id: String) -> AnyPublisher<AccessibilityImagesResult, APIError>
i have screen, where i need call fetchStandarts
, fetchAccessibilities
, fetchImages
together for displaying screen.
But, for fetchStandarts
and fetchAccessibilities
i need to do multi requests, for example i have
let standartsIDs = ["1", "2"]
let accessibilityIDs = ["3", "4"]
for this case i need call fetchStandarts
2 times and fetchAccessibilities
2 times.
What i did, firstly i generate all Publishers
let standardsPublishers = ["1", "2"]
.map({ key -> AnyPublisher<[A11yStandart], APIError> in
a11yStandartsRepository.fetchStandarts(by: key)
})
let answersPublishers = ["3", "4"]
.map { key -> AnyPublisher<AccessibilitiesResult, APIError> in
accessibilitiesRepository.fetchAccessibilities(by: key)
}
let imagesPublisher = a11ImagesRepository.fetchImages(id: "1")
now its most difficult part for me, because i dont know how better "merge"(by merge i don't mean merge operator in combine) all of publishers.
I tried do like this:
Publishers.MergeMany(standardsPublishers)
.zip(Publishers.MergeMany(answersPublishers))
.zip(imagesPublisher)
.collect()
and like this
Publishers.Zip(
Publishers.MergeMany(standardsPublishers),
Publishers.MergeMany(answersPublishers)
)
.zip(imagesPublisher)
but im filing that im doing wrong this. Can you please give me advice, how i need correct "merge"(by merge i don't mean merge operator in combine) all publishers in one and receive data
I think understand what you're asking, but if not, we can try again.
Here is a Playground:
import UIKit
import Combine
enum APIError : Error {
}
typealias DownloadedThing = String
func makeRequest(downloading: String) -> Future<DownloadedThing, APIError> {
Future { continuation in
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + .seconds((1...5).randomElement()!)) {
print("Downloaded \(downloading)")
continuation(.success("Downloaded \(downloading)"))
}
}
}
var thingsToDownload = ["Cats", "Dogs", "Elephants", "Ducks"]
let subscription = thingsToDownload
.publisher
.flatMap(makeRequest)
.sink { completion in
switch completion {
case .finished:
print("All the requests are done")
case .failure(let apiError):
print("An API error caused a problem \(apiError)")
}
} receiveValue: { results in
debugPrint(results)
}
In the playground, the list of things to be downloaded is in thingsToDownload
. I also have a function, makeRequest(downloading:)
that issues a request to download one thing. In this case it returns a Future
. A Future
is a Publisher
that will emit one value or fail. If you want to, you can use .eraseToAnyPublisher()
to erase the types, but I've left it as a Future
so that my code communicates the fact that the request will either succeed or fail.
Inside of makeRequest
the Future
that gets returned waits a random amount of time then succeeds. It simulates a network request that takes a few seconds to complete.
The main thing you were asking about is in the pipeline at the bottom. I get the array and ask for its publisher
so each of the values is passed as a value to the pipeline that follows.
The important operator in the pipeline is flatMap
. flatMap
passes each of the values to my makeRequest(downloading:)
. As each publisher is returned, flatMap
sets up a listener waiting for that publisher to complete before passing the result of the publisher down the pipeline.
At the bottom is the sink
call. As each of the publishers that are run by flatMap
completes, this sink receives the resulting value and prints it to the console. If it were possible for my mock API calls to fail, then failures would also be delivered here.
Running this playground on my system once yields:
"Downloaded Ducks"
"Downloaded Cats"
"Downloaded Dogs"
"Downloaded Elephants"
All the requests are done
and a second time gives:
"Downloaded Ducks"
"Downloaded Elephants"
"Downloaded Cats"
"Downloaded Dogs"
All the requests are done
So each result is delivered as it completes, and a message arrives when all the requests are done.