1

I'm exploring Combine Swift with this project https://github.com/sgl0v/TMDB and I'm trying to replace its imageLoader with something that supports Combine: https://github.com/JanGorman/MapleBacon

The project has a function that returns the type AnyPublisher<UIImage?, Never>. But the imageLoader MapleBacon library returns the type AnyPublisher<UIImage, Error>.

So I'm trying to convert types with this function:

func convert(_ loader: AnyPublisher<UIImage, Error>) -> AnyPublisher<UIImage?, Never> {
    // here.
}

I actually found a question that is kinda similar to mine, but the answers weren't helpful: https://stackoverflow.com/a/58234908/3231194


What I've tried to so far (Matt's answer to the linked question).

The sample project has this function:

func loadImage(for movie: Movie, size: ImageSize) -> AnyPublisher<UIImage?, Never> {
        return Deferred { return Just(movie.poster) }
            .flatMap({ poster -> AnyPublisher<UIImage?, Never> in
                guard let poster = movie.poster else { return .just(nil) }
                let url = size.url.appendingPathComponent(poster)
                let a = MapleBacon.shared.image(with: url)
                    .replaceError(with: UIImage(named: "")!) // <----
            })
            .subscribe(on: Scheduler.backgroundWorkScheduler)
            .receive(on: Scheduler.mainScheduler)
            .share()
            .eraseToAnyPublisher()
    }

if I do replaceError,

I get the type Publishers.ReplaceError<AnyPublisher<UIImage, Error>>


BUT, I was able to solve this one, by extending the library.

extension MapleBacon {
    public func image(with url: URL, imageTransformer: ImageTransforming? = nil) -> AnyPublisher<UIImage?, Never> {
      Future { resolve in
        self.image(with: url, imageTransformer: imageTransformer) { result in
          switch result {
          case .success(let image):
            resolve(.success(image))
          case .failure:
            resolve(.success(UIImage(named: "")))
          }
        }
      }
      .eraseToAnyPublisher()
    }
}
Glenn Posadas
  • 12,555
  • 6
  • 54
  • 95
  • @JoakimDanielson IDK, just a challenge, like the whole sample project has that type. I too want an optional UIImage versus an UIImage with Error publisher in a ViewModel (just like in the sample project). – Glenn Posadas Apr 13 '21 at 09:42
  • That seems like a perfect duplicate target. How didn't those answers solve your problem? – Dávid Pásztor Apr 13 '21 at 09:53
  • @DávidPásztor See the comment in the answer to the linked question. I actually tried it, but it seems it's just getting more complicated for me. One solution that I'm looking at now is to just extend the library. – Glenn Posadas Apr 13 '21 at 10:03
  • 1
    Can you show what you have tried from that post? What has "got more complicated"? Matt's answer should do the job. – Sweeper Apr 13 '21 at 10:10
  • Thanks @Sweeper. See my edit. I added my current solution too. – Glenn Posadas Apr 13 '21 at 10:42
  • Side note: rather than changing the code in `loadImage`, I think it's better to create your own conformance of the protocol `ImageLoaderServiceType`, and pass that to the initialiser of `MoviesUseCase`. Just saying. – Sweeper Apr 13 '21 at 11:07

1 Answers1

6

First, you need to map a UIImage to a UIImage?. The sensible way to do this is of course to wrap each element in an optional.

Then, you try to turn a publisher that sometimes produces errors to a publisher that Never produces errors. You replaceError(with:) an element of your choice. What element should you replace errors with? The natural answer, since your publisher now publishes optional images, is nil! Of course, assertNoFailure works syntactically too, but you might be downloading an image here, so errors are very likely to happen...

Finally, we need to turn this into an AnyPublisher by doing eraseToAnyPublisher

MapleBacon.shared.image(with: url)
    .map(Optional.some)
    .replaceError(with: nil)
    .eraseToAnyPublisher()
Sweeper
  • 213,210
  • 22
  • 193
  • 313