Swift Combine: Create your own Subscriber
Looking to clean up your subscriptions? You can make a separate file and a subscriber! This is how you do it.
Subscriber Protocol
class StringSubscriber: Subscriber {
}
Let’s go ahead and create subscriber class and I will call it StringSubscriber.
Make your class conform to Subscriber
protocol.
Now, you can obviously create a new file and you can do that over there.
But since you are simply just learning about creating your subscribers, I’m going to create in the ViewController.swift file. But in your actual application, you can create a separate file.
Typealias from Subscriber
class StringSubscriber: Subscriber {
typealias Input = Type
typealias Failure = Type
}
When your class conforms to Subscriber
protocol, you can see two typealias.
- Input means that this particular string subscriber is going to be working on this kind of datatype.
- Failure means what kind of error you expect from the subscriber.
class StringSubscriber: Subscriber {
typealias Input = String
typealias Failure = Never
}
For the input type, I specified String data type because StrinbSubscriber
is going to handle subscription from the publisher as String Values.
For the failure, I specified Never
, which means that it’s never going to fail.
Methods
class StringSubscriber: Subscriber {
typealias Input = String
typealias Failure = Never
func receive(subscription: Subscription) {
}
func receive(_ input: String) -> Subscribers.Demand {
}
func receive(completion: Subscribers.Completion<Never>) {
}
}
When conforming to Subscriber
protocol, so there are three different functions that you need to provide or implement in your StringSubscriber.
func receive(subscription: Subscription)
gets subscription.func receive(_ input: String) -> Subscribers.Demand
gets the value.func receive(completion: Subscribers.Completion<Never>)
gets the completion.
Before go ahead, let’s look at the relationship between a publisher and a subscriber.
Publisher Subscriber Relationship
So, by looking at this diagram, you can see the relationship between a publisher and a subscriber.
Step 1: subscriber subscribes to a publisher.
Step 2: Then, the publisher creates subscription, and gives it back to the subscriber.
Step 3: The subscriber now can request the values from the publisher.
Step 4: The publisher can return values.
Step 5: Finally, the publisher sends a completion when the values are completed.
That’s the normal flow between the publisher and the subscriber.
So now you have a little bit of idea of how the things are flowing between the publisher and the subscriber.
1. func receive(subscription: Subscription)
// When the subscriber is going to receive the subscription
func receive(subscription: Subscription) {
print("Received Subscription")
}
When you are receiving a subscription, you can also request that how many items you want to receive.
// When the subscriber is going to receive the subscription
func receive(subscription: Subscription) {
print("Received Subscription")
subscription.request(.max(3))
}
I set the limit of receiving only three items.
Whenever you’re setting the maximum items, you are telling publisher that “Hey, I’m only interested in three items, so don’t send me more.”
This thing is called the backpressure
.
- Backpressure simply means that your publisher might be sending you one hundred items or a lot of data, but you’re specifying how many items you want to receive from the publisher.
func receive(_ input: String) -> Subscribers.Demand
func receive(_ input: String) -> Subscribers.Demand {
}
This method is where you are actually receiving the value.
func receive(_ input: String) -> Subscribers.Demand {
print("Received Value", input)
return .none
}
There are multiple options for this method. You can override func receive(subscription: Subscription)
you set previously. For example, if you return unlimited
from this method, you are actually overriding func receive(subscription: Subscription)
.
Overriding receive(subscription: Subscription)
// When the subscriber is going to receive the subscription
func receive(subscription: Subscription) {
print("Received Subscription")
subscription.request(.max(3)) // backpressure
}
// Where you receives the acutal value.
func receive(_ input: String) -> Subscribers.Demand {
print("Received Value", input)
return .unlimited
}
By doing this, the option you made on func receive(subscription: Subscription)
will be ignored.
Not overriding receive(subscription: Subscription)
// When the subscriber is going to receive the subscription
func receive(subscription: Subscription) {
print("Received Subscription")
subscription.request(.max(3)) // backpressure
}
// Where you receives the acutal value.
func receive(_ input: String) -> Subscribers.Demand {
print("Received Value", input)
return .none
}
If you are returning .none
from func receive(_ input: String) -> Subscribers.Demand
, subscription.request(.max(3))
will remain valid.
func receive(completion: Subscribers.Completion<Never>)
func receive(completion: Subscribers.Completion<Never>) {
print("Completed")
}
This is going to be sent to the subscriber when the publisher is finished publishing the event.
Use your custom subscriber
let publisher = ["A", "B", "C", "D", "E", "F"].publisher
let subscriber = StringSubscriber()
/*
publisher.sink { _ in
print("completion")
} receiveValue: { value in
print("value")
}
*/
publisher.subscribe(subscriber)
Instead of sink
, now you can use your custom subscriber.
Conclusion
- Conform to
Subscriber
- Specify data type you are handling with and error type
- Customize your subscription with
func receive(subscription: Subscription)
andfunc receive(_ input: String) -> Subscribers.Demand
- Handle Data with
func receive(_ input: String) -> Subscribers.Demand
- Handle Completion with
func receive(completion: Subscribers.Completion<Never>)