Custom binding operator in Combine

August 19, 2019

For the last couple of years I have been using ReactiveSwift to use reactive paradigms in my apps. And I couldn't be happier when Apple announced Combine, the new shiny reactive framework. Though when I jumped into it, it didn't feel like home right away. The two frameworks felt a bit different even though they both follow the same principles of reactive stream.

One of the missing pieces is the binding operator <~ which I think everyone in the ReactiveSwift community is fond of. So, I thought let's try to recreate it using Combine as it shouldn't be too hard. You can read more about the operator here though in nutshell (the simplified version) it allows a subscriber (which recieves values) to subscribe to a publisher (which emits values). One can picture the following relationship between a publisher and a subscriber using the custom operator as below,

subscriber <~ publisher

Cool, so looking at the above, we need three things i.e. a publisher, a subscriber and a way to bind them.

Below we create our custom operator which takes two parameters i.e. a publisher & a subscriber but we don't bind them yet,

Step 1

func <~ (publisher: Publisher, subscriber: Subscriber) {
    // some way to bind `publisher` & `subscriber`

Now go ahead and run it (assuming you declared the operator and added precedencegroup), it won't compile ๐Ÿ˜“

image 1

Looks like we can't use Publisher & Subscriber as regular parameters, so let's use them as generic constraints instead as suggested by the compiler,

Step 2

func <~ <S: Subscriber, P: Publisher> 
		(subscriber: S, publisher: P) 
    // some way to bind `publisher` & `subscriber`

And voila it compiles now, feeling pretty good right? ๐Ÿ˜„

Cool so now let's implement the next part i.e. find a way to bind a publisher and subscriber. Looking at the apis which are part of Publisher protocol, there's a function func subscribe<S: Subscriber>(_ subscriber: S) which, as per the documentation, attaches the specified subscriber to a publisher which is exactly what we need. Let's go ahead and use it,

Step 3

func <~ <S: Subscriber, P: Publisher> 
		(subscriber: S, publisher: P) 

Though again it won't compile ๐Ÿ˜“

image 2

This time it says it can't figure out the generic requirement. Not to worry, let's checkout the declaration of the func subscribe(:).

extension Publisher {

    /// Attaches the specified subscriber to this publisher.   
    public func subscribe<S>(_ subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input

So looking at it, we need to fulfill a requirement i.e. Self.Failure == S.Failure, Self.Output == S.Input. Let's break it down one by one and understand what it means.

The first constraint says Self.Failure == S.Failure which means that we need to make sure that the failure type of the publisher should match the failure type of its subscriber. Similarly Self.Output == S.Input means that we need to match the publisher's output type to its subscriber's input type which makes sense as well because we can only assign String to a label's text property (.text) and not a color. Cool so let's add the extra requirement we need,

Step 4

func <~ <S: Subscriber, P: Publisher> 
		(subscriber: S, publisher: P) 
		where S.Input == P.Output, S.Failure == P.Failure 

And voila it compiles again ๐Ÿ˜„

Now that we have successfully made our custom operator, it's time to see it in action.

// publisher
@Published var text: String? = ""

// subscriber
let labelTextSubscriber = Subscribers.Assign(
	object: label, 
	keyPath: \.text

// subscribe using operator
labelTextSubscriber <~ $text

// subscribe without operator

Thanks for reading ๐Ÿ™Œ You can reach out to me at @riteshhh on twitter ๐Ÿ˜„

Ritesh Gupta

Hi, Iโ€™m Ritesh Gupta, iOS Engineer from India ๐Ÿ‡ฎ๐Ÿ‡ณ. Here, I mainly write about Swift and iOS app development.
twitter โ†’ @_riteshhh.
More about me.