-1

I have zipped two publishers in the function, which downloads users and vehicles with a backend API:

   func fetchUserAndvehicles() {
       Publishers.Zip(UserApiClient().getUser(), VehicleApiClient().getVehicles())
           .eraseToAnyPublisher()
           .receive(on: DispatchQueue.main)
           .sink(receiveCompletion: { [weak self] completion in
               switch completion {
               case .failure(let error):
                   self?.errorHandling.showErrorAlert(error)
               case .finished:
                   break
               }
               }, receiveValue: { [weak self] user, vehicles in
                   // store vehicles in the user object
           })
           .store(in: &subscriptions)
   }

Each of the vehicles have an imageUrl that can be used to download an image of the vehicle. This works fine. But I would like to download the images, if any, before I store the vehicles in the user object. Is it possible to use the same combine pipeline to do this? I tried with a flatMap, but that resulted in a compile error.

The following is following the excellent answer from Cristik. It looks ok, but Xcode flags the flatMap line with No exact matches in call to instance method 'flatMap':

let vehiclesPublisher = VehicleApiClient().getVehicles()
    .flatMap { vehicles in
        Publishers.Zip(Just(vehicles).setFailureType(to: Error.self), Publishers.MergeMany(vehicles.map { VehicleApiClient().getImage(at: $0.url)}).collect())
    }
    .map {
        return $0.0
    }

The vehicles have an optional property that needs to be unwrapped, but that isn't the cause of the compile error.

Ivan C Myrvold
  • 680
  • 7
  • 23
  • 1
    Let's say you manage to have a compilable code that starts downloading all the images, but what do you do if some of them fail? Do you still want to assign to the user? Also, how's the image download looking on your program, do you have publishers for that? – Cristik Sep 30 '21 at 18:33
  • If the download of the image for a vehicle fails, I still want to assign the vehicle to the user. I have a publisher for the image download. – Ivan C Myrvold Oct 01 '21 at 05:26

1 Answers1

0

What you need can is to

  1. Convert the vehicles publisher to an array of image downloader publishers
  2. Create a single publisher from a list of publishers
  3. Wait for that publisher

Assuming you have a downloadImage(from url: URL) function somewhere, and that your Vehicle type has an imageURL property, what you have to do in order to accomplish what you need, is to

let vehiclesPublisher = VehicleApiClient().getVehicles()
    .flatMap { vehicles in
        Publishers.Zip(Just(vehicles).setFailureType(to: Error.self), // setFailureType dance needed
                       Publishers.MergeMany(vehicles.map { downloadImage(from: $0.imageURL) }).collect())
    }.map {
        return $0.0 // unpack the vehicles, so we have back a publisher that outputs [Vehicle]
    }

Publishers.Zip(UserApiClient().getUser(), vehiclesPublisher)
    // rest of the pipeline stays the same

What happens in the above code is we transform the array of vehicles to an array of image downloaders, which is waited for with the collect() operator. But we also need to retain the vehicles array, so a zip is needed, followed by an unpack of the first item returned by zip, as we don't care about the image download status, we only care for all of them to finish.

Cristik
  • 30,989
  • 25
  • 91
  • 127
  • That looks awesome. But I had a compile error for the `vehiclesPublisher`: `No exact matches in call to instance method 'flatMap'`. I have updated the code with your suggestion. – Ivan C Myrvold Oct 01 '21 at 15:08
  • @IvanCMyrvold that kinda falls under the umbrella of a new question. Given the fact that you gave very few details about the api clients you have, it would be hard for me to provide a code that works in your codebase. – Cristik Oct 01 '21 at 15:54