'Swift Combine - Accessing separate lists of publishers

I have two lists of URLs that return some links to images. The lists are passed into a future like

static func loadRecentEpisodeImagesFuture(request: [URL]) -> AnyPublisher<[RecentEpisodeImages], Never> {
        return Future { promise in
            print(request)
             networkAPI.recentEpisodeImages(url: request)
                .sink(receiveCompletion: { _ in },
                        receiveValue: { recentEpisodeImages in
                            promise(.success(recentEpisodeImages))
                        })
                .store(in: &recentImagesSubscription)
        }
        .eraseToAnyPublisher()
    }

Which calls:

    /// Get a list of image sizes associated with a featured episode .
    func featuredEpisodeImages(featuredUrl: [URL]) -> AnyPublisher<[FeaturedEpisodeImages], Error> {
        let featuredEpisodesImages = featuredUrl.map { (featuredUrl) -> AnyPublisher<FeaturedEpisodeImages, Error> in
        return URLSession.shared
            .dataTaskPublisher(for: featuredUrl)
            .map(\.data)
            .decode(type: FeaturedEpisodeImages.self, decoder: decoder)
            .receive(on: networkApiQueue)
            .catch { _ in Empty<FeaturedEpisodeImages, Error>() }
            .print("###Featured###")
            .eraseToAnyPublisher()
        }
        return Publishers.MergeMany(featuredEpisodesImages).collect().eraseToAnyPublisher()
    }
    /// Get a list of image sizes associated with a recent episode .
    func recentEpisodeImages(recentUrl: [URL]) -> AnyPublisher<[RecentEpisodeImages], Error> {
        let recentEpisodesImages = recentUrl.map { (recentUrl) -> AnyPublisher<RecentEpisodeImages, Error> in
        return URLSession.shared
            .dataTaskPublisher(for: recentUrl)
            .map(\.data)
            .decode(type: RecentEpisodeImages.self, decoder: decoder)
            .receive(on: networkApiQueue)
            .catch { _ in Empty<RecentEpisodeImages, Error>() }
            .print("###Recent###")
            .eraseToAnyPublisher()
        }
        return Publishers.MergeMany(recentEpisodesImages).collect().eraseToAnyPublisher()
    }

and is attached to the app state:

/// Takes an action and returns a future mapped to another action.
     static func recentEpisodeImages(action: RequestRecentEpisodeImages) -> AnyPublisher<Action, Never> {
        return loadRecentEpisodeImagesFuture(request: action.request)
             .receive(on: networkApiQueue)
             .map({ images in ResponseRecentEpisodeImages(response: images) })
             .replaceError(with: RequestFailed())
             .eraseToAnyPublisher()
     }

It seems that:

return Publishers.MergeMany(recentEpisodes).collect().eraseToAnyPublisher()

doesn't give me a reliable downstream value as whichever response finishes last overwrites the earlier response.

I am able to log the responses of both series of requests. Both are processing the correct arrays and returning the proper json.

I would like something like:

return recentEpisodeImages

but currently this gives me the error

Cannot convert return expression of type '[AnyPublisher<RecentEpisodeImages, Error>]' to return type 'AnyPublisher<[RecentEpisodeImages], Error>'

How can I collect the values of the inner publisher and return them as

AnyPublisher<[RecentEpisodeImages], Error>


Solution 1:[1]

Presuming that the question is how to turn an array of URLs into an array of what you get when you download and process the data from those URLs, the answer is: turn the array into a sequence publisher, process each URL by way of flatMap, and collect the result.

Here, for instance, is how to turn an array of URLs representing images into an array of the actual images (not identically what you're trying to do, but probably pretty close):

func publisherOfArrayOfImages(urls:[URL]) -> AnyPublisher<[UIImage],Error> {
    urls.publisher
        .flatMap { (url:URL) -> AnyPublisher<UIImage,Error> in
            return URLSession.shared.dataTaskPublisher(for: url)
                .compactMap { UIImage(data: $0.0) }
                .mapError { $0 as Error }
                .eraseToAnyPublisher()
        }.collect().eraseToAnyPublisher()
}

And here's how to test it:

let urls = [
    URL(string:"http://www.apeth.com/pep/moe.jpg")!,
    URL(string:"http://www.apeth.com/pep/manny.jpg")!,
    URL(string:"http://www.apeth.com/pep/jack.jpg")!,
]
let pub = publisherOfArrayOfImages(urls:urls)
pub.sink { print($0) }
    receiveValue: { print($0) }
    .store(in: &storage)

You'll see that what pops out the bottom of the pipeline is an array of three images, corresponding to the array of three URLs we started with.

(Note, please, that the order of the resulting array is random. We fetched the images asynchronously, so the results arrive back at our machine in whatever order they please. There are ways around that problem, but it is not what you asked about.)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1