I want to move a CoreImage task to the background...

It feels like this should be easy, but I'm having conceptual problems about how to do this. Any help would be appreciated.

I have a sample app below that works exactly as expected. I'm able to use the Slider and Stepper to generate inputs to a function that uses CoreImage filters to manipulate my input image. This all works as expected, but it's doing some O(n) CI work on the main thread, and I want to move it to a background thread. I'm pretty sure this can be done using combine, here is the pseudo code I imagine would work for me:

    func doStuff() {
        // subscribe to options changes
        // .receive on background thread
        // .debounce
        // .map { model.inputImage.combine(options: $0)
        // return an object on the main thread.
        // update an @Published property?
    }

Below is the POC code for my project. Any guidance as to where I should use combine to do this would be greatly appreciate. (Also, please let me know if you think combine is not the best way to tackle this. I'd be open to alternative implementations.)

struct ContentView: View {
    @State private var options = CombineOptions.basic
    @ObservedObject var model = Model()
    
    var body: some View {
        VStack {
            Image(uiImage: enhancedImage)
                .resizable()
                .aspectRatio(contentMode: .fit)
            Slider(value: $options.scale)
            Stepper(value: $options.numberOfImages, label:
                        {
                Text("\(options.numberOfImages)")})
        }
    }
    
    private var enhancedImage: UIImage {
        return model.inputImage.combine(options: options)
    }
}

class Model: ObservableObject {
    let inputImage: UIImage = UIImage.init(named: "IMG_4097")!
}

struct CombineOptions: Codable, Equatable {
    static let basic: CombineOptions = .init(scale: 0.3, numberOfImages: 10)
    
    var scale: Double
    var numberOfImages: Int
}

Replies

I think I've found something that works. Moved CombineOptions into the model, and subscribe to the options changes in the Model's init. Still, I'd appreciate any feedback on this code below.

struct ContentView: View {
    @ObservedObject var model = Model()
    
    var body: some View {
        VStack {
            Image(uiImage: model.composed)
                .resizable()
                .aspectRatio(contentMode: .fit)
            Slider(value: $model.options.scale)
            Stepper(value: $model.options.numberOfImages, label:
                        {
                Text("\(model.options.numberOfImages)")})
        }
        .padding()
    }
    
    private var enhancedImage: UIImage {
        return model.inputImage.combine(options: model.options)
    }
}

class Model: ObservableObject {
    let inputImage: UIImage = UIImage.init(named: "IMG_4097")!
    @Published var options = CombineOptions.basic
    @Published var composed: UIImage
    private var cancellables: [AnyCancellable] = []
    
    init() {
        self.composed = inputImage
        $options
            .debounce(for: 1.0, scheduler: DispatchQueue.global(qos: .background))
            .map( { mapOptions in
                return UIImage.composed(from: self.inputImage, using: mapOptions)
            } )
            .receive(on: DispatchQueue.main)
            .assign(to: \.composed, on: self)
            .store(in: &cancellables)
    }
}