October 3, 2019

Combine's Sequence Publisher Missing First Element

Just had a fun run in with a bug in Combine. Any Sequence can produce a Publisher that publishes each element in the sequence when you subscribe to it.

You might have a custom sequence implementation that counts from 1 to 9:

final class Incrementer {
    var value = 0

    func next() -> Int? {
        value += 1
        guard value < 10 else {
            return nil
        }
        return value
    }
}

extension Incrementer: Sequence {
    func makeIterator() -> AnyIterator<Int> {
        return AnyIterator {
            self.next()
        }
    }
}

If you now create an instance of this Incrementer you can see that Combine has automatically provided a publisher that you can subscribe to:

let i = Incrementer()
i.publisher

“That’s very neat”, you think to yourself. So you go to try it out, just something simple at first. Like this:

i.publisher.sink { v in
   print(v)
}

// Output:
2
3
4
5
6
7
8
9

Cool! Huh, wait a second. Where’s 1? I have no idea actually. This works as expected:

let i = Incrementer()
for x in i {
    print(x)
}

So it doesn’t seem related to the Sequence implementation.

Anyway, adding a .map { $0 } before calling .sink solves the issue.

let i = Incrementer()
i.publisher.map { $0 }.sink { v in
    print(v)
}


// Output:
1
2
3
4
5
6
7
8
9

🤷‍♂️

Thanks to Simon Westerlund for helping me investigate this. If anyone at Apple happens to read this I’ve filed a Feedback: FB7343531.