Introduction to Reactive Programming with Combine— Part 1

Ahmad G. Sufi
11 min readApr 11, 2023

--

An Overview of the Combine Framework: Insights from Reading, Research, and Videos.

  • Imperative vs Declarative Programming
  • What is Functional Reactive Programming?
  • Introduction to Combine: An Overview of Apple’s Reactive Framework
  • What is Functional reactive programming?
  • When to Use Combine in Your iOS Development Projects
  • The Foundations of Combine
  • Core Concepts of Combine: Publisher, Subject, Operator, Subscribe, subscription, Scheduler.
  • Advantages of Using Combine Over Traditional Code
  • Key Takeaways

Imperative VS Declarative

Imperative programming and declarative programming are two contrasting programming paradigms that differ in how they approach problem-solving and programming.

Imperative programming is a programming paradigm in which the program specifies the exact steps and procedures that the computer must execute to achieve the desired result. In other words, the program consists of a sequence of statements that tell the computer how to perform a task. Imperative programming is typically characterized by the use of control structures such as loops, conditionals, and functions that change the state of the program.

On the other hand, declarative programming is a programming paradigm in which the program specifies what the computer must accomplish, rather than how it must accomplish it. In declarative programming, the programmer specifies the rules and constraints that define the problem, and the computer determines how to solve the problem based on those rules and constraints. Declarative programming is typically characterized by the use of abstractions such as functions, relations, and logical rules.

What is Functional reactive programming?

(FRP) is a programming paradigm that is used to handle asynchronous data streams and events. It is based on the functional programming concept of applying functions to a data stream and treating functions as first-class citizens, combined with reactive programming, which is a way of modeling asynchronous data flows.

The key advantages of FRP are that it provides a declarative and composable way to manage asynchronous events and data streams, making it easier to reason about and test. It can also help to simplify code and reduce the amount of code needed to manage asynchronous events and states.

What is Combine?

The Combine framework is a powerful functional reactive programming that provides a declarative approach to how your app processes events. Rather than potentially implementing multiple delegate callbacks or completion handler closures, you can create a single processing chain for a given event source. Each part of the chain is a Combine operator that performs a distinct action on the elements received from the previous step.

When to use Combine

Combine fits most naturally when you want to set up something that reacts to a variety of inputs. User interfaces fit very naturally into this pattern.

Some things you can do with Combine include:

  1. Manage asynchronous events: The Combine framework provides a way to handle asynchronous events in a declarative and reactive way, making it easy to handle asynchronous data streams and events.
  2. Reactively update user interfaces: Combine makes it easy to update the user interface based on changes in data or state, allowing for more reactive and efficient UI updates.
  3. Simplify error handling: Combine provides operators with handling errors concisely and effectively, reducing the amount of code needed for error handling.
  4. Improve code readability: Combine makes it easier to write more declarative and functional code, which can be easier to read and understand compared to traditional imperative programming.

Foundation of Combine

Declarative, reactive programming isn’t a new concept. It’s been around for quite a while, but it’s made a fairly noticeable comeback in the last decade.

For Apple’s platforms, there have been several third-party reactive frameworks like RxSwift, which implements the Rx standard; ReactiveSwift, directly inspired by Rx; Interstellar, which is a custom implementation and others.

Combine implements a standard that is different but similar to Rx, called Reactive Streams. Reactive Streams has a few key differences from Rx, but they both agree on most of the core concepts.

In iOS 13/macOS Catalina, however, Apple brought reactive programming support to its ecosystem via the built-in system framework, Combine.

You’ve probably used at last some of the following in your apps:

  • NotificationCenter
  • The delegate pattern
  • Grand Central Dispatch and Operations
  • Closures

Combine introduces a common, high-level language to the Swift ecosystem to design and write asynchronous code.

Apple has integrated Combine into its other frameworks too, so Timer, NotificationCenter, and Core frameworks like Core Data already speak its language. Luckily, Combine is also very easy to integrate into your own code.

Finally, last but not least, Apple designed its amazing UI framework, SwiftUI, to integrate easily with Combine as well.

To give you an idea of how committed Apple is to reactive programming with Combine, here’s a simple diagram showing where Combine sits in the system hierarchy:

Core Concepts Of Combine

  • Publisher
  • Subjects
  • Operators
  • Subscribers and Subscriptions
  • Scheduler

Publishers

Publishers are types that can emit values over time to one or more interested parties, such as subscribers. A publisher that has not had any subscription requests will not provide any data. Regardless of the internal logic of the publisher, which can be pretty much anything including math calculations, networking, or handling user events, every publisher can emit multiple events of these three types:

  1. An output value of the publisher’s generic Output type.
  2. A successful completion.
  3. A completion with an error of the publisher’s Failure type.

A publisher can emit zero or more output values, and if it ever completes, either successfully or due to a failure, it will not emit any other events.

One of the best features of publishers is that they come with error handling built in; error handling isn’t something you add optionally at the end if you feel like it.

The Publisher protocol is generic over two types, as you might have noticed in the diagram earlier:

  • Publisher. Output is the type of output value of the publisher. If it’s an Int publisher, it can never emit a String or a Date value.
  • Publisher. Failure is the type of error the publisher can throw if it fails. If the publisher can never fail, you specify that by using a Never failure type.

When you subscribe to a given publisher, you know what values to expect from it and which errors it could fail with.

Subjects

Subjects are a special case of publishers that also adhere to the Subject protocol. This protocol requires subjects to have a .send(_:) method to allow the developer to send specific values to a subscriber (or pipeline).

Subjects can be used to “inject” values into a stream, by calling the subject’s .send(_:) method. This is useful for integrating existing imperative code with Combine.

A subject can also broadcast values to multiple subscribers. If multiple subscribers are connected to a subject, it will fan out values to the multiple subscribers when send(_:) is invoked. A subject is also frequently used to connect or cascade multiple pipelines together, especially to fan out to multiple pipelines.

A subject does not blindly pass through the demand from its subscribers. Instead, it provides an aggregation point for demand. A subject will not signal demand to its connected publishers until it has received at least one subscriber itself. When it receives any demand, it then signals for unlimited demand to connected publishers. With the subject supporting multiple subscribers, any subscribers that have not requested data with demand are not provided the data until they do.

There are two types of built-in subjects with Combine: CurrentValueSubject and PassthroughSubject. They act similarly, the difference being CurrentValueSubject remembers and requires an initial state, where PassthroughSubject does not. Both will provide updated values to any subscribers when .send() is invoked.

Both CurrentValueSubject and PassthroughSubject are also useful for creating publishers for objects conforming to ObservableObject. This protocol is supported by several declarative components within SwiftUI.

Operators

Operators are methods declared on the Publisher protocol that returns either the same or a new publisher. That’s very useful because you can call a bunch of operators one after the other, effectively chaining them together.

Because these methods, called “operators”, are highly decoupled and composable, they can be combined (aha!) to implement very complex logic over the execution of a single subscription.

It’s fascinating how operators fit tightly together like puzzle pieces. They cannot be mistakenly put in the wrong order or fit together if one’s output doesn’t match the next one’s input type:

Publisher and Operation combine:

As a bonus, operators always have input and output, commonly referred to as upstream and downstream — this allows them to avoid shared state.

Operators focus on working with the data they receive from the previous operator and provide their output to the next one in the chain. This means that no other asynchronously-running piece of code can “jump in” and change the data you’re working on.

Subscribers

Finally, you arrive at the end of the subscription chain: Every subscription ends with a subscriber. Subscribers generally do “something” with the emitted output or completion events.

Currently, Combine provides two built-in subscribers, which make working with data streams straightforward:

  • The sink subscriber allows you to provide closures with your code that will receive output values and completions. From there, you can do anything your heart desires with the received events.
  • The assign subscriber allows you to, without the need for custom code, bind the resulting output to some property on your data model or on a UI control to display the data directly on-screen via a key path.

Should you have other needs for your data, creating custom subscribers is even easier than creating publishers. Combine uses a set of very simple protocols that allow you to be able to build your own custom tools whenever the workshop doesn’t offer the right one for your task.

How Combine works together:

Subscription

a subscription is an object that represents the connection between a publisher and a subscriber. When you subscribe to a publisher, you create a subscription object that allows you to receive values from the publisher.

The subscription is responsible for requesting values from the publisher, as well as handling the receipt of those values. When you create a subscription, you typically specify a closure that will be called for each value that is received from the publisher. You can also specify closures for handling errors and completion events.

Subscriptions are important to Combine because they allow you to control the flow of data from publishers to subscribers. You can use subscriptions to:

  • Start and stop receiving values from a publisher
  • Limit the number of values that are received from a publisher
  • Combine multiple subscriptions into a single subscription
  • Cancel a subscription to stop receiving values from a publisher

Scheduler

Schedulers are objects in the Combine framework that define the execution context for publishers and subscribers. They specify which queue or thread a publisher or subscriber should execute on. Schedulers are important for controlling the timing and concurrency of events in the Combine framework.

Schedulers can be thought of as a kind of “traffic controller” for data streams. They determine when and where events are processed and can help ensure that events are processed in a thread-safe and efficient manner. By using schedulers, developers can control the timing and concurrency of events in their reactive applications, ensuring that they behave predictably and efficiently.

Schedulers in Combine are represented by the Scheduler protocol. There are several built-in implementations of the Scheduler protocol, including DispatchQueue, RunLoop, and OperationQueue. These implementations provide different execution contexts for publishers and subscribers.

What’s the benefit of Combine code over “standard” code?

You can, by all means, never use Combine and still create the best apps out there. There’s no argument about that. You can also create the best apps without Core Data, URLSession, or UIKit. But using those frameworks is more convenient, safe, and efficient than building those abstractions yourself.

Combine (and other system frameworks) and aim to add another abstraction to your async code. Another level of abstraction on the system level means tighter integration that’s well-tested and a safe-bet technology.

It’s up to you to decide whether Combine is a great fit for your project or not, but here are just a few “pro” reasons you might not have considered yet:

  • Combine is integrated on the system level. That means Combine itself uses language features that are not publicly available, offering you APIs that you couldn’t build yourself.
  • Combine abstracts many common operations as methods on the Publisher protocol and are already well tested.
  • When all of your asynchronous pieces of work use the same interface — Publisher — composition, reusability becomes extremely powerful.
  • Combine’s operators are highly composable. If you need to create a new one, that new operator will instantly plug and play with the rest of Combine.
  • Combine asynchronous operators are already tested. All that’s left for you to do is test your business logic.
  • As you see, most of the benefits revolve around safety and convenience. Combined with the fact that the framework comes from Apple, investing in writing Combine code looks promising.

Key points

  • Combine is a declarative, reactive framework for processing asynchronous events over time.
  • It aims to solve existing problems, like unifying tools for asynchronous programming, dealing with mutable states, and making error handling a starting team player.
  • Combine revolves around publishers to emit events over time, operators to asynchronously process and manipulate upstream events, subscribers to consume the results and do something useful with them, subscriptions to control the connection between a publisher and a subscriber, and a scheduler to define the execution context for publishers and subscribers.

Ref:

Combine: Asynchronous Programming with Swift third edition.

Using Combine: Joseph Heck

If you have any questions or comments on this tutorial, do not hesitate to contact me: Linkedin, Twitter, or Email: alsofiahmad@yahoo.com.

Thanks for reading!😀

--

--