What is the most idiomatic way to process a value a given number of times when that value is derived from another publisher and the resultant value is used in the next iteration?
If the value didn't come from another publisher I would use reduce
.
Since I own this code I can rewrite to not return a publisher but I would like to know how this situation should be handled using Combine.
import Combine
import Foundation
let iterations = 1000
let initialValue = 0
let intSubject = PassthroughSubject<Int, Never>()
// Iterate over a value a number of times passing the result to the next iteration.
// This seems a lot like reduce but the problem is the reduce closure returns a value not a publisher.
let x = intSubject
.flatMap { number in
// This is the important part. The number from the previous iteration
// is needed to create the next publisher. In the real life scenario this
// result object is more complex than an Int. Integer addition is used
// here as an example I am not looking for a way to solve integer addition.
return number.addOne()
}
.handleEvents(receiveOutput: { (number: Int) in
DispatchQueue.main.async {
intSubject.send(number)
}
return
})
.handleEvents(receiveRequest: { _ in
DispatchQueue.main.async {
intSubject.send(initialValue)
}
})
.prefix(iterations)
.eraseToAnyPublisher()
let subscription = x.sink(receiveValue: { number in print("\(number)") })
extension Int {
func addOne() -> AnyPublisher<Int, Never> {
// Only returns a single value and completes
[self].publisher.map { $0 + 1 }.eraseToAnyPublisher()
}
}
UPDATE 1
I decided perhaps my contrived example was concealing some things so at the risk of complicating things here is my real scenario. I was able to make a small simplication by using CurrentValueSubject
instead of PassthroughSubject
which handles the initial value nicely.
I am still not convinced that using handleEvents
is the best way to propagate updated values back for the next iteration. Additionally I don't want to use .sink
as I want to pass the publisher on outside of this function for later subscription.
public func evolve(generations: Int, offspringCount: Int, populationLimit: Int, randomNumberGenerator: RandomNumberSource) -> AnyPublisher<GenePool<Environment>, GenePoolError> {
let genePoolSubject = CurrentValueSubject<GenePool<Environment>, Never>(self)
return genePoolSubject
.flatMap { genePool -> AnyPublisher<GenePool<Environment>, GenePoolError> in
genePool.evolve(
offspringCount: offspringCount,
populationLimit: populationLimit,
randomNumberGenerator: randomNumberGenerator
)
}
.handleEvents(receiveOutput: { (pool: GenePool<Environment>) in
DispatchQueue.main.async {
genePoolSubject.send(pool)
}
})
.prefix(generations)
.eraseToAnyPublisher()
}
private func evolve(offspringCount: Int, populationLimit: Int, randomNumberGenerator: RandomNumberSource) -> AnyPublisher<GenePool<Environment>, GenePoolError> { ... }