Search code examples
iosswiftcombine

Swift, Combine, cancel on the flight and replace with the new request operator


I've got this network call for fetching images.

func load() {
        guard let url = URL(string: urlString)
        else { return }

        subscription = URLSession.shared.dataTaskPublisher(for: url)
            .map({ UIImage(data: $0.data) })
            .replaceError(with: nil)
            .receive(on: RunLoop.main)
            .sink(receiveValue: { [weak self] in self?.image = $0 })
    }

This is triggered by the text field being filled. After each letter is typed, I would like my publisher to wait with the execution for let's say 2 seconds, unless a user typed another letter. If that happens, I'd like the timer to reset to 2 seconds again.

Is there any on the fly cancel operator if the new request has been sent in the meanwhile?

Thanks for all the help.


Solution

  • This is a pretty standard thing to do with a reactive library and easily accomplished. The switchToLatest operator is the one that does the magic.

    @available(iOS 14.0, *)
    final class ViewController: UIViewController {
        var textField: UITextField!
        var image: UIImage?
        var cancelBag = Set<AnyCancellable>()
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            textField.textPublisher // this is from the `CombineCocoa` library
                .debounce(for: 2, scheduler: RunLoop.main)
                .compactMap { makeURL(from: $0) }
                .map {
                    URLSession.shared.dataTaskPublisher(for: $0)
                        .catch { _ in Empty() } // what do you want to do with loading errors?
                }
                .switchToLatest()
                .map { UIImage(data: $0.data) }
                .receive(on: RunLoop.main)
                .sink(receiveValue: { [weak self] in self?.image = $0 })
                .store(in: &cancelBag)
        }
    }
    
    func makeURL(from: String?) -> URL? {
        // build and return the correct URL for the image request
        fatalError()
    }