Step into Swift Combine
What is Combine?
A unified declarative API for processing values over time.
Often in our code, we have many places where we have some sort of value or event Publisher and some Subscriber is interested in receiving values from that Publisher. And some interested party comes along and establishes a connection between these Publisher and Subscriber. Once the connection is established, the Subscriber sometimes declares that they are interested in receiving values from that Publisher (upstream), after which the Publisher is free to begin sending values to Subscriber (downstream). This goes on until either the Publisher decides to stop sending values, whether because it finished or there was some sort of failure, or by someone choosing to cancel the subscription. This general shape of communication appears throughout our software, whether it’s callbacks or closures or any other situations where there’s asynchronous communication. — Combine in Practice, WWDC 2019
Key Concepts
Combine has following three concepts: Publishers
, Subscribers
, and Operators
.
Publishers
Publishers are the declarative part of Combine’s API which describe how values and errors are produced and allow registration of a Subscriber to receive these values over time. They specify two associatedTypes
and a key function as shown below:
Output
is the type of value Publisher it produces.Failure
is the kind of errors that Publisher produces. If Publisher does not produce an error, then we can use type never for that associated type.subscribe<S: Subscriber>(_ subscriber: S)
is the key function that describes how to attach a Subscriber to a Publisher with the generic constraints that the associated types must match the Subscriber’sInput
to Publisher’sOutput
, and the Subscriber’sFailure
to Publisher’sFailure
.
Here are some additional convenience publishers:
Just: It provides a single result and then terminates, it takes a failure type of <Never>
Future: A future is initialized with a closure that eventually resolves to a single output value or failure completion.
Published: A property wrapper adds a Combine publisher to any property.
Published wraps the variable, address
, and will trigger events whenever the variable is mutated. Any subscriber(s) subscribing to the property will also receive any initial value being set upon the property’s initialization.
The below example is the implementation of Publisher protocol:
Subscribers
Subscribers are the counterparts of Publishers. They receive values, including the completion if the Publisher is finite. As Subscribers usually act and mutate state upon receipt of values, so they are reference types in Swift; in other word, they are classes. Below is the protocol of Subscriber:
Subscriber has two associatedTypes
as shown above. One is Input
and the other is Failure
. Subscriber has three well-defined event functions to receive a subscription, values and a completion as described below:
- A Publisher will call
receive(subscription: Subscription)
just once in response to a subscribe call. - A Publisher can then call
receive(_ input: Input)
to provide values to the Subscriber. - Once the Publisher has finished or a failure has occurred it sends at most a
completion
signal and no further values are emitted by a Publisher once thatcompletion
has been signalled.
There are two special Subscribers that takes burden from us:
sink(receiveCompletion:receiveValue:)
: Sink takes two closures.receiveCompletion
executes upon subscribers completion and it is anenum
that indicates whether the publisher finished normally or failed with an error andreceiveValue
triggers every time it receives an element from subscribed publisher.
assign(to:on:)
: It immediately assigns every element it receives to a property of a given object using key path to indicate the property
Operator
While working with Combine we often come to a scenario where output of the Publisher does not match the input of the Subscriber. Operators are the real saviors for such cases. They subscribe to Publishers (upstream), describe a behavior for changing values and then send the result to a Subscriber (downstream). Based on actions, Operators can be divided into the three following types:
1. Transformation Operators:
map
: it operates just like Swift standard library map, except it operates on Publishers.
flatMap
: it tears down an observable sequence where elements of the observable itself are observables and merges the resulting observable sequences into one observable sequence.flatMap
is mostly used in error handling scenarios.
2. Combination Operators:
merge
: it combines multiple Observables into one by merging their elements
combineLatest
: it combines the latest elements from two or more Publishers when any one of the Publishers emits an element.
combineLatest
zip
: this is similar to Combine Latest but unlike Combine Latest Zip waits for each Publisher to emit new elements.
3. Filtering and other Operators:
There are a lot more operators like filter removeDuplicates, debounce, count, contains and more which are self explanatory. I encourage you to go to Apple’s documentation and try some yourself.
Subject
Subjects are special types of Publisher in Combine in which we can subscribe as well as send events to them dynamically.
As shown in the above image, Subject describes two functions. One is to send value and other is to send completion with failure.
Combine provides two very handy subjects:
- PassthroughSubject: It sends every element after subscription.
- CurrentValueSubject: It behaves same as the PassthroughSubject but unlike PassthroughSubject, CurrentValueSubject stores the most recent elements and send to new subscribers.
Scheduler
receive
: it takes a single required parameter (on:
) which accepts a scheduler, and an optional parameter (optional:
)
examplePublisher.receive(on: RunLoop.main)
subscribe
: Subscribe defines the scheduler on which to run a publisher in a pipeline.
networkDataPublisher
.subscribe(on: backgroundQueue) // 1
.receive(on: RunLoop.main) // 2
.assign(to: \.text, on: nameLabel) // 3
- The
subscribe
call requests the publisher (and any pipeline invocations before this in a chain) be invoked on the backgroundQueue. - The
receive
call moves the data to the main thread to update the UI elements - The
assign
call uses the assign subscriber to bind data to a KVO compliant object.
AnyCancellable
AnyCancellable is used to keep the reference of a subscriber so that we can clean up the subscriber on deallocation.
var cancellable: AnyCancellable?let sinkSubscriber = aPublisher
.sink { data in
print("received ", data)
}
cancellable = AnyCancellable(sinkSubscriber)
Source Code:
Related Articles:
Problem Solving with Combine Swift
Getting started with the Combine framework in Swift
Conclusion
Combine is a very powerful framework, introduced in Swift 5.1. It enables developers to dive into reactive programming. Apple ingeniously designed this language feature which goes hand in hand with Apple’s new declarative UI framework SwiftUI yet keeping UIKit unchanged.
Though Combine itself is a huge topic to write on, in this article I have provided explanations and covered the basics to start with.
Later I have tried to cover most commonly used keywords in Combine with diagrams and code snippets.
Finally, I shared a working example of Combine with SwiftUI.
I hope that you have enjoyed this article. I encourage you to read my other articles on Swift Type Erasure, Custom UIView from .xib and TableView Prefetching DataSource using Swift
Thank you all for your attention 🙏🏻. Feel free to tweet and get connected.
Visit our website to learn more about us:
www.monstar-lab.co.bd