Home > database >  How to capture the first value in a publisher one way and the rest another way
How to capture the first value in a publisher one way and the rest another way

Time:09-15

I have a publisher where I want to handle receiving the first value one way, and receiving the rest another way. Currently I have the following:

let bar = foo
    .first()
    .sink { value in
    print("first value is \(value)")
}

let baz = foo
    .dropFirst()
    .sink { value in
        print("one of the rest is \(value)")
    }

foo.send(1)
foo.send(2)
foo.send(3)
foo.send(4)
foo.send(5)
foo.send(6)

This works. But it forces me to have two subscriptions, one for the first value, and another for the rest. Is there a clever way of combining the two into one subscription (without external flag management). Something like:

let bar = foo
    .first()
    .sink { value in
        print("first value is \(value)")
    }
    .dropFirst()
    .sink {
        print("the rest is \(value)")
    }

I'm aware the above doesn't make sense (and is probably uglier than the original solution) since a publisher is singular flow that doesn't branch (beyond value, completion, error), but that's due to my lack of creativity, I'm hoping someone here has something that I might be overlooking.

CodePudding user response:

One typical approach is to use something like .scan to map into a tuple that tells the rest of the pipeline whether this is the first one or not.

In this simple example, the "rest of the pipeline" is simply .sink, but you can see that it does know which one is first.

class ViewController: UIViewController {
    var cancellables = Set<AnyCancellable>()
    let subject = PassthroughSubject<Int, Never>()
    override func viewDidLoad() {
        super.viewDidLoad()
        var firstPassed = false
        subject
            .scan((0, false)) {prev, this in
                defer { firstPassed = true }
                return (this, firstPassed)
            }
            .sink {
                if !$0.1 {
                    print($0.0, "is the first one")
                } else {
                    print($0.0, "is not the first one")
                }
            }
            .store(in: &cancellables)
        subject.send(1)
        subject.send(2)
        subject.send(3)
        subject.send(4)
        subject.send(5)
    }
}

Result:

1 is the first one
2 is not the first one
3 is not the first one
4 is not the first one
5 is not the first one

CodePudding user response:

var cancellables = Set<AnyCancellable>()

let foo = PassthroughSubject<Int, Never>()
let first = foo
    .first()

let restOfFirst = foo
    .dropFirst()
    
Publishers.CombineLatest(first, restOfFirst) // <= here
    .sink {
        print("first value: \($0), rest Of other values\($1)")
    }
    .store(in: &cancellables)

Resualt:

foo.send(1)
foo.send(2)
foo.send(3)
foo.send(4)
foo.send(5)
foo.send(6)

first value: 1, rest Of other values: 2
first value: 1, rest Of other values: 3
first value: 1, rest Of other values: 4
first value: 1, rest Of other values: 5
first value: 1, rest Of other values: 6
  • Related