I have built a simplified combine pipeline in my Xcode Playground, to make car objects [CarWithImage]
from an array of cars, [Car]
. That seems to work fine. But I would like the pipeline to check each car object for imageString
, and if it isn't nil fetch it with the function getImage(_:)
. I have commented that code out, because I get the error type of expression is ambiguous without more context
and I don't know how to fix that. I would also like to introduce a delay in the pipeline to more realistically simulate a network download of cars and images, and set the CarWithImage
image property to nil if the image fetching fails.
I have a Xcode Playground repository on GitHub where you can test out my code. First page is with the original class, the second page is with trying out compactMap: Cars Playground
The code will run in Xcode Playground:
import UIKit
import Combine
struct Car {
let name: String
let imageString: String?
}
struct CarWithImage {
let name: String
let image: UIImage?
}
final class CarClass {
let myCars = [Car(name: "Tesla", imageString: "car"), Car(name: "Volvo", imageString: nil)]
let delayCar = 4
let delayImage = 6
func getVehicles() -> AnyPublisher<[CarWithImage], Error> {
myCars.publisher
.flatMap { car in
// if let imageString = car.imageString {
// getImage(imageString)
// .flatMap { image in
// return Just(CarWithImage(name: car.name, image: image))
// }
// }
return Just(CarWithImage(name: car.name, image: nil))
}
.collect()
.flatMap { cars in
cars.publisher.setFailureType(to: Error.self)
}
.collect()
.eraseToAnyPublisher()
}
func getImage(_ string: String) -> AnyPublisher<UIImage, Error> {
Just(UIImage(systemName: string)!)
.flatMap { image in
Just(image).setFailureType(to: Error.self)
}
.eraseToAnyPublisher()
}
}
let carClass = CarClass()
carClass.getVehicles()
.sink(receiveCompletion: { print($0)}) { cars in
cars.forEach { car in
let haveImage = car.image != nil
let string = haveImage ? "and it have an image" : ""
print("The car is", car.name, string)
}
}
// This is just to check that the getImage function works
carClass.getImage("car")
.sink(receiveCompletion: { print($0)}) { image in
print("Got image", image)
}
After suggestion to use compactMap, I have modified the class, but now I only get cars when the car have an image:
final class CarClass {
let myCars = [Car(name: "Tesla", imageString: "bolt.car"), Car(name: "Volvo", imageString: nil)]
let delayCar = 4
let delayImage = 6
func getVehicles() -> AnyPublisher<[CarWithImage], Error> {
myCars.publisher
.flatMap { car in
self.getImage(car.imageString)
.compactMap { $0 }
.flatMap { image in
return Just(CarWithImage(name: car.name, image: image))
}
}
.collect()
.flatMap { cars in
cars.publisher.setFailureType(to: Error.self)
}
.collect()
.eraseToAnyPublisher()
}
func getImage(_ string: String?) -> AnyPublisher<UIImage?, Error> {
guard let imageString = string else { return Just(nil).setFailureType(to: Error.self).eraseToAnyPublisher() }
return Just(UIImage(systemName: imageString))
.flatMap { image in
Just(image).setFailureType(to: Error.self)
}
.eraseToAnyPublisher()
}
}