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,
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 ๐
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,
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,
func <~ <S: Subscriber, P: Publisher>
(subscriber: S, publisher: P)
{
publisher.subscribe(subscriber)
}
Though again it won't compile ๐
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,
func <~ <S: Subscriber, P: Publisher>
(subscriber: S, publisher: P)
where S.Input == P.Output, S.Failure == P.Failure
{
publisher.subscribe(subscriber)
}
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
$text.subscribe(labelTextSubscriber)
Thanks for reading ๐ You can reach out to me at @riteshhh on twitter ๐
Hi, Iโm Ritesh Gupta, iOS Engineer from India ๐ฎ๐ณ. Here, I mainly write about Swift and iOS app development.
twitter โ @_riteshhh.
More about me.