Combine: Reimplementing the URLSession `dataTask` Publisher in 3 different ways
This article focus on providing you some possible implementations of a Publisher
to the URLSession
API request. In Combine, a Publisher is simply an instance that emits values in an asynchronous way. As we described in this past article. There are three entities that work together to broadcast Publisher
outputs to multiple receivers:
- Publishers: Emit asynchronous values that may be transformed, filtered delayed or replicated via operators.
- Subscribers: Subscribe to the publisher events and listen to all the emitted values.
- Subscriptions: Establish a link between the Publisher and its Subscribers saving that connection in memory and creating a channel for multiple subscribers to receive the values.
All those stakeholders are represented by protocols and their tasks should follow a specific order that makes Combine work properly. That's why Apple discourages us to implement each of the protocols, if someone who's not aware of how it should works try to implement them in the wrong way, Combine may not attend his necessities. Instead, Apple provides us the operators and pre-implemented publishers to fill all our necessities. But as we desire here to understand how it works under the hood, we are about to reimplement the URLSession
publisher in three different secure ways.
Implementing Publisher, Subscription and Subscriber
As we did in the past article, we are about to implement each of the entities involved in the URLSession
publisher and establish a communication channel between them. First, let's create a new Subscription
subclass, that saves the link between a Publisher and one of its Subscribers:
As you can see, our URLSessionSubscription
class saves the subscriber that just attached to the publisher, and having an instance of both URLSession
and URLRequest
allows the subscription to start the API request with that data and retrieve the results via closure, as we originally did before Combine existed. But now, after retrieving the response, if we get an error, we may automatically broadcast that as a completion of the Publisher
or, if we get a success response, we shall send the value straight to the Subscriber
via its interface methods.
As the Subscription
protocol establishes, we shall have a cancel
method in order to deallocate the Subscriber
and avoid a retain cycle.
Now let's implement the Publisher
:
The Publisher only instantiates the Subscription that will guarantee the Subscriber which is attached will receive the value and sends that to the subscriber itself in order to demand the values. Finally, we should create a new Publisher
instance to our original class:
Now, we can attach any subscriber that shall receive the values from the publisher:
This is the log after creating this subscription:
Using a Future Publisher
For those who don't know, a Future
publisher is used for when we want to subscribe to a block of code that emits a value(or a failure) in an asynchronous way. For example, as we are about to illustrate, we may fetch data from the web using URLSession
and retrieve the response in a completion, which may use the promise
closure parameter from the Future
to send the results to the Publisher
.
Observe how we can do that:
What's happening is basically that we are calling our former imperative method dataTask
with a URLRequest
instance to fetch data from the Web. Every time we get a response, may it be an error or a success response, we use the promise
closure from the Future
to emit that asynchronous output through the Publisher. This is the best way to return an asynchronous output from an imperative function as a Combine Publisher. It shall be used in other use cases as well, but we focus on our URLSession
scenario.
Use a PassthroughSubject
Instead of a Future
in order to return an asynchronous value you may rely on a Subject to that. What you should do is just create a PassthroughSubject
that returns the output of the API call and return it in the main thread as an AnyPublisher
. The class that calls it will receive the Publisher and subscribe to it, and as soon as the API returns some response, it may be sent through the PassthroughSubject
.
All the outputs are sent through the subject and may be subscribed.
Conclusion
It's very important to understand how our most used frameworks and APIs work under the hood and how are their implementation. That facilitates our usage and turns us into better developers. We provided three different ways to implement a Publisher to URLSession that may also be applied to create publishers to any kind of asynchronous data resource. I hope you are now ready to deliver values via Combine in any of your own libraries and enjoyed it ;).