How I started with Combine Framework

Julián Astrada
4 min readJul 21, 2023

Every change is an opportunity

At my current job we recently bumped the minimum iOS version we support on the app. We went from iOS 12 to iOS 13, and I was really excited because that meant we could finally start working with Combine!

In modern app development, writing clean, maintainable, and efficient code is essential for a successful project. Swift, as a powerful and expressive language, offers various tools and frameworks to achieve this goal. One such tool is the Combine framework, that allows developers to handle asynchronous and event-driven code elegantly.

In this article, we’ll explore the concept of refactoring Swift code using the Combine framework, understanding its key components, and how it can significantly improve reactive programming.

Understanding Refactoring

Since we had a good amount of code on our app with a lot of things we would have done differently if we knew where we were heading, we took the opportunity to refactor.

Refactoring is the process of restructuring code without changing its external behavior. It aims to improve code readability, maintainability, and performance while reducing technical debt. As software projects grow in complexity, refactoring becomes an essential practice to ensure that the codebase remains scalable and maintainable.

With traditional completion-based asynchronous code, developers may encounter callback hell, where nested closures lead to convoluted and difficult-to-read code. Refactoring with Combine helps alleviate this issue by offering a more streamlined and declarative approach to handle asynchronous operations.

Why Combine Framework?

Combine is a declarative Swift framework that facilitates reactive programming by enabling developers to work with asynchronous and event-driven code in a more functional and composable manner. It allows us to model and manipulate streams of data, combining the power of functional programming and reactive programming paradigms.

Combine’s key advantages include:

  • Reactive Approach: Combine encourages the reactive programming paradigm, where data flows through the system, and changes in data automatically propagate through the pipeline. This paradigm helps developers manage complex data dependencies effectively.
  • Declarative Code: Combine promotes declarative code, where the focus is on “what” needs to be done rather than “how” it should be done. This leads to more concise and expressive code, reducing the potential for bugs.
  • Error Handling: Combine provides streamlined error handling mechanisms, making it easier to manage errors across asynchronous operations, resulting in more robust and resilient code.
  • Unified API: Combine unifies various async APIs in the iOS/macOS ecosystem, including KVO, Notification Center, and URLSession, providing a consistent approach for handling data streams.

Key Components of Combine

To leverage Combine effectively, it’s crucial to understand its key components:

  1. Publishers: Publishers represent streams of data that emit values over time. They can be thought of as data producers. Publishers come with various operators to transform and filter data before passing it along the chain. Common publishers include Future, Just, NotificationCenter.Publisher, and URLSession.DataTaskPublisher.
  2. Subscribers: Subscribers listen to the publishers and receive the emitted values. They can be thought of as data consumers. Subscribers define how they will react to the received values, like updating UI elements, performing side effects, or transforming data further downstream. Common subscribers include sink, assign, and onReceive.
  3. Operators: Combine provides a wide range of operators that allow you to manipulate and transform data streams. Operators are used to apply filtering, mapping, combining, and other transformations on the data, making the data processing pipeline highly flexible. Some of the commonly used operators include map, filter, flatMap, merge, and combineLatest.

Refactoring regular code with Combine

Let’s walk through an example of refactoring a classic completion-based asynchronous code into a Combine-based approach. Consider a scenario where we have a function that fetches user data from a server:

With Combine, we can refactor this code to utilize Future and flatMap operators for a more elegant and concise approach:

By using Combine, we’ve encapsulated the completion handler within a Future, which acts as a Publisher emitting a single value. The .eraseToAnyPublisher() is necessary to hide the internal implementation and present the result as an AnyPublisher.

Benefits of Refactoring with Combine

Improved Readability: Combine’s functional and declarative approach makes the code more concise and easier to understand. The use of operators allows developers to express data transformations more clearly.

Error Handling: Combine makes it easier to handle errors across the entire data processing chain, ensuring that error propagation is seamless and comprehensible.

Reusability: Combine allows developers to create reusable data processing pipelines, which can be used in multiple parts of the application without rewriting the same logic.

Debugging and Testing: Combine facilitates better debugging and unit testing since the data flow is explicitly defined by the pipeline of operators.

Conclusion

Refactoring Swift code using the Combine framework can greatly enhance the overall code quality and maintenance of your iOS/macOS applications. By embracing the reactive programming paradigm and leveraging Combine’s powerful operators, developers can create more elegant and efficient code that is easier to read, write, and maintain. As you gain familiarity with Combine, you’ll find it to be a valuable tool in your arsenal for building modern and responsive applications. Adopting Combine can lead to a more scalable and maintainable codebase, making it an essential skill for every Swift developer in the modern iOS/macOS ecosystem.

--

--