Closures Vs. Combine Vs. Async Await

InRhythm™
6 min readApr 25, 2023

--

Introduction

Design Credit: Joel Colletti, Lead UI/UX Designer @ InRhythm

There are three methods of asynchronous coding in Swift: Closures (i.e. completion handlers), Combine, and Async/Await.

Closures are a fundamental feature of Swift that allow developers to define self-contained blocks of functionality, while Combine provides a modern way to handle asynchronous events and data streams. Async/Await is a new feature that makes it easier to write asynchronous code that looks and feels like synchronous code, improving the readability and maintainability of Swift code.

  • Closures — Introduced in Swift 2.0, WWDC 2015
  • Combine — iOS 13 and macOS Catalina in 2019
  • Async/Await — Swift 5.5 WWDC 2021

Asynchronous Coding Methods

Closures

In Swift programming, closures are a powerful and versatile feature that allow you to capture and store functionality for later use. A closure is a self-contained block of code that you can pass around and execute at a later time, similar to a function or method.

A common use case for closures is handling asynchronous operations, such as network requests or animations. Defining a closure that executes when the operation completes helps you to keep your code organized and avoid callback functions.

You can use closures in Swift in different ways:

  • As a function argument: Pass the closure as a function argument to define a custom behavior tailored to a specific use case.
  • As a function return value: As the return value of a function, you can create functions that generate other functions.
  • As a variable: You can assign a closure to a variable for later use.

Closures can capture and retain references to values outside of their own scope which allows you to define closures that can access and modify variables defined outside of their own function or method.

Combine

Combine is a powerful framework that allows developers to handle asynchronous events and data streams in a more intuitive and functional way. The Combine framework provides a declarative way to define and manipulate data streams using a set of operators that can transform, filter, and combine data in various ways.

Key use cases for the Combine framework include:

  1. Handling asynchronous events: Combine allows developers to handle asynchronous events, such as network requests or user input, in a more elegant and concise way than traditional callback-based APIs
  2. Managing data streams: Combine provides a unified way to manage data streams from various sources, such as user input, network requests, and local storage
  3. Composing reactive UI: Combine can be used to create reactive UI, where the UI updates automatically in response to changes in the underlying data
  4. Testing: Combine’s declarative syntax makes it easy to write unit tests for asynchronous code

The key building blocks of Combine are publishers and subscribers. Publishers are objects that emit a stream of values over time, while subscribers consume these values and perform some action in response. Publishers can be transformed, combined, and filtered using various operators to create complex data pipelines.

In summary, the Combine framework provides a powerful way to handle asynchronous events and data streams in Swift programming.

Async/Await

Async/Await provides a more intuitive and concise way to write asynchronous code. With Async/Await, developers can write asynchronous code that looks and feels like synchronous code, which makes it easier to read, write, and maintain.

Previously, developers used callbacks or closures to handle the results of asynchronous operations which often lead to complex and difficult-to-read code, as well as potential issues with callback hell and race conditions.

With Async/Await, developers can use familiar keywords like Await and Async to write asynchronous code, which allows them to pause the execution of a function until a result is available, without blocking the main thread. This makes it easier to write code that is both asynchronous and easy to understand.

For example, this code uses Async/Await to perform a network request:

In the example, fetchUser() function is marked as Async, indicating that it is an asynchronous function. Inside the function, the await keyword is used to wait for the result of the network request, without blocking the main thread. The result is then returned as a User object.

Overall, Async/Await is a powerful new feature that makes it easier to write asynchronous code in Swift, while also improving readability and maintainability.

Comparison

Features

Note that while Closures and Combine are both used for handling asynchronous operations, they operate at different levels of abstraction. Closures are used for defining code blocks with captured values, whereas Combine is a reactive programming framework that provides a unified way to manage asynchronous data streams. Async/Await, on the other hand, is a language-level feature that simplifies the process of writing asynchronous code by allowing developers to write asynchronous code that looks and feels like synchronous code.

Side-By-Side Code Function Definition

The Closure example code is fairly complex and a developer would need to examine the entire code block to be certain of its operation.

In the Combine block, the code is slightly less complex. The addition of .map, .decode, and .mapError help make it more understandable.

In the Async/Await code block, not only is there less code, it is also easier to understand what the code does: Set the path, create the request, make the call, and send the data back.

The code is still doing all of the “heavy lifting” for asynchronous coding, but it doesn’t LOOK as though it is doing the heavy lifting.

Side-By-Side Function Usage

The comparison of the three approaches really becomes clear when you look at the synchronizing calls inside of the same function.

As you can see, the result of the first call (i.e. getting a list of food by name) must come back to complete the second call (i.e. getting a list of foods that have the same “category” as the initial result).

Managing the response from the initial call is rather difficult, especially if we need to obtain sensible information about an error when something does go wrong. That difficulty only increases when we become reliant not only on a successful response, but also rely on the data as a result of that second response to power the second request.

Resources

Combine

Async Await

Originally published at www.inrhythm.com and written by Mike Adams, Senior Technical Writer @ InRhythm.

--

--

InRhythm™

We are Knowledge Driven, Agile Mobile and Web Consultants. 🚀