Swift Combine .repeat
Asked Answered
R

2

6

I'd like to create a repeat functionality that creates a loop in my code using Combine. I noticed that Combine does not have a repeat publisher via this great repo: https://github.com/freak4pc/rxswift-to-combine-cheatsheet. Heres the code that I wrote that works to repeat 2 states. How do I reduce this to something more readable or create my own repeat function?

toggleShouldDisplay = Just<Void>(())
  .delay(for: 2, scheduler:RunLoop.main)
  .map({ _ in
    self.shouldDisplay = true
    self.didChange.send(())
  })
  .delay(for: 2, scheduler: RunLoop.main)
  .map({ _ in
    self.shouldDisplay = false
    self.didChange.send(())
  })
  .setFailureType(to: NSError.self)
  .tryMap({ _ in
    throw NSError()
  })
  .retry(.max) // I might hit Int.max if I reduce the delays
  .sink(receiveValue: { _ in
    //Left empty
  })
Renn answered 12/7, 2019 at 0:2 Comment(0)
D
6

The .retry(_:) operator is really intended to be used for retrying operations that can fail, such as network requests. It sounds like you need a timer instead. Luckily, as of Xcode 11 beta 2, Apple has added Publisher support to the Timer class in Foundation.

One other comment about your implementation: I assume this code is used in a BindableObject because you are accessing didChange. Since didChange can be any kind of Publisher, why not use your shouldDisplay property as the Publisher?

final class MyModel: BindableObject {
    var didChange: CurrentValueSubject<Bool, Never> { shouldDisplaySubject }
    var shouldDisplay: Bool { shouldDisplaySubject.value }

    private let shouldDisplaySubject = CurrentValueSubject<Bool, Never>(false)
    private var cancellables: Set<AnyCancellable> = []

    init() {
        startTimer()
    }

    private func startTimer() {
        Timer.publish(every: 2, on: .main, in: .default)
            .autoconnect()
            .scan(false) { shouldDisplay, _ in
                !shouldDisplay
            }
            .assign(to: \.value, on: shouldDisplaySubject)
            .store(in: &cancellables)
    }
}
Doublejointed answered 13/7, 2019 at 21:1 Comment(0)
J
3

You can use Timer.Publisher like this:

toggleShouldDisplay = Timer.publisher(every: 2, on: .main, in: .default)
  .autoconnect()
  .sink {
    self.shouldDisplay = !self.shouldDisplay
    self.didChange.send(())
  }

autconnect() lets the Timer start as soon as you subscribe using sink(_:).

Jenaejenda answered 16/7, 2019 at 8:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.