kkkk

Swift Concurrency Adoption at Halodoc

Swift Concurrency May 19, 2023

At Halodoc, we are constantly trying to adopt the latest feature from Swift releases, to improve performance, simplified code and better readability. Async await is part of the new structured concurrency changes that arrived in Swift 5.5. This is a very simplified description, but it should give you an idea already how important concurrency in Swift is for the performance of iOS apps. With the new async methods and await statements, we can define methods performing work asynchronously.

Asynchronous programming is an essential part of modern app development. Promises and futures are powerful abstractions for managing asynchronous operations in Swift. One popular way to implement asynchronous programming in iOS is through PromiseKit, a third-party library that provides a convenient way to work with Promises. Promises are a way of handling asynchronous operations by providing a placeholder object that will eventually contain the result of the operation.

However, with the introduction of Swift Concurrency in Swift 5.5, developers now have a new, built-in way to manage concurrency that helps to remove the third-party dependency. In this blog post, we'll explore how we achieved the PromiseKit behaviour using Swift concurrency.

Benefits of Swift Concurrency over PromiseKit

  1. Improved performance: Swift Concurrency is built into the Swift language and runtime, which means that it can be more efficient than third-party libraries like PromiseKit. By using Swift Concurrency, we can avoid the overhead of using an external library and improve the performance of our app.
  2. Simplified code: With Swift Concurrency, we can write asynchronous code that looks more like synchronous code, making it easier to read and understand.
  3. Compatibility: It is native with Swift 5.5, which means that it is always available and up-to-date. This can reduce the risk of compatibility issues and ensure that our code is always using the latest and greatest features of Swift.
  4. Better error handling:  It makes error handling more explicit and easier.
  5. Reduce app size: App size significantly reduce by removing the third party dependencies.

What is Swift Concurrency?

Swift Concurrency is a framework for writing concurrent and asynchronous code in Swift that provides a set of new language features and tools. It aims to make it easier for developers to write efficient and scalable code that takes advantage of multiple processors and cores, solving the challenges associated with concurrent programming. To use Swift Concurrency, developers need to be using Xcode 13 or later and Swift 5.5 or later, and they can take advantage of new language features such as async/await syntax and actors. The Swift Package Manager also includes support for concurrency, making it easy to incorporate third-party libraries and tools that take advantage of these new features.

It introduces two new keywords: async and await. async is used to define asynchronous functions, while await is used to wait for the result of an asynchronous function.

“Await is awaiting a callback from his buddy async”

How to transition from PromiseKit to Swift Concurrency

To replace PromiseKit with native Swift async/await syntax the first step in transitioning from PromiseKit to Swift Concurrency is to replace PromiseKit with the new async/await syntax. This involves replacing PromiseKit's Promise object with the native Swift Task object.

Here's how we use PromiseKit to fetch data from a remote API:

To do the same thing with Swift Concurrency, we can use the async/await syntax:

As we can see, the code is much simpler and easier to read.

Use Swift Concurrency's provides set of APIs that we can use to implement asynchronous tasks. These include async let, which allows us run multiple tasks concurrently, and TaskGroup, which allows us group and manage a set of tasks.

Here's how we use async let to fetch data from multiple endpoints concurrently:

Where we will be using URLSession for fetch the data as

This function returns a promise that will eventually contain a User object. It accomplishes this by creating a new promise using Promise { seal in }, and then using URLSession to perform an asynchronous HTTP request. When the request completes, the promise is either fulfilled with the User object, or rejected with an error.

Swift Concurrency makes it easier to write concurrent code. async is used to define asynchronous functions, while await is used to wait for the result of an asynchronous function. Here's an example:

  • This function does the same thing as the fetchUserData() function we defined earlier, but it uses Swift Concurrency instead of PromiseKit.
  • The async keyword indicates that this function is asynchronous, and the await keyword is used to wait for the result of the data(from:) method call.
  • This function is much simpler than the PromiseKit version, and it uses the built-in URLSession API instead of the PromiseKit wrapper. Because it uses async and await, we don't need to create a new promise, and we don't need to use the Promise class at all.

Grouping of Async and Sync Operation  

  • To get user and bank list we are grouping two independent async function in when(fullfilled:). and in order to get wallet detail, saved cards, and order list we have dependency on user, so we have group them use .then and .done.

We are calling it from ViewController.

This is the output of above implementation -

Output

Below code is  implementation for same using swift concurrency.

  • here we are using async keyword to declare that getUser(), getBankList() are independent asynchronous functions, and only than getWallet(user:) is getting called as it is waiting for response.
  • we are using using await keyword we are making them all  dependent ie getWallet(user:), getSavedCardList() getOrderHistory(for:).

In addition to being simpler, this version of the function is also more efficient. When we use PromiseKit, we create a new promise object for each asynchronous task we perform. This can add overhead, and it can make it harder to reason about our code. With Swift Concurrency, we can use a single function that performs the asynchronous task.

Challenges while adopting Swift Concurrency

While replacing PromiseKit with Swift Concurrency offers many benefits, there are also some challenges we face. Here are a few of them:

  1. Compatibility: Swift Concurrency is only available in Swift 5.5 and later versions, so if we're using an older version of Swift, we won't be able to use it.
  2. Debugging: Debugging concurrency issues can be challenging, especially if we're not used to working with async/await syntax. we may need to spend more time debugging our code to ensure that it's functioning as expected.
  3. Error handling: Error handling in Swift Concurrency is different from PromiseKit. With PromiseKit, we can use a catch block to handle errors, while with Swift Concurrency, we use try/catch blocks. This means that we'll need to update our error handling code. For example Let's say we have an asynchronous function that loads data from a remote API using PromiseKit, and we want to handle any errors that might occur during the loading process:
Error Handling using PromiseKit
  • In this example, we're using PromiseKit's catch block to handle any errors that might occur during the loading process.
  • If the API call fails or the status code is invalid, we throw a custom DataLoadingError error and catch it in the catch block.

Here's an implementation of the same function using Swift Concurrency:

Error Handling using Swift Concurrency:
  • In this example, we're using a try/catch block to handle any errors that might occur during the loading process.
  • If the API call fails or the status code is invalid, we throw a custom DataLoadingError error and catch it in the catch block.
  • With PromiseKit, we use a catch block to handle errors, while with Swift Concurrency, we use a try/catch block.

Conclusion

In conclusion, Swift Concurrency offers a native way to handle asynchronous programming in Swift. While PromiseKit is a popular library in the Swift community, it's becoming less relevant as developers transition to using Swift Concurrency. Replacing PromiseKit with Swift Concurrency requires learning a new set of APIs and syntax, but the benefits of Swift Concurrency, such as the simplification of async code, make the transition worthwhile. By leveraging Swift Concurrency's Task-based concurrency model and async/await syntax, we can manage async code more effectively and with greater readability just like we did at Halodoc.

Join us

Scalability, reliability, and maintainability are the three pillars that govern what we build at Halodoc Tech. We are actively looking for engineers at all levels and if solving hard problems with challenging requirements is your forte, please reach out to us with your resumé at careers.india@halodoc.com.

About Halodoc

Halodoc is the number 1 all around Healthcare application in Indonesia. Our mission is to simplify and bring quality healthcare across Indonesia, from Sabang to Merauke. We connect 20,000+ doctors with patients in need through our Tele-consultation service. We partner with 3500+ pharmacies in 100+ cities to bring medicine to your doorstep. We've also partnered with Indonesia's largest lab provider to provide lab home services, and to top it off we have recently launched a premium appointment service that partners with 500+ hospitals that allow patients to book a doctor appointment inside our application. We are extremely fortunate to be trusted by our investors, such as the Bill & Melinda Gates Foundation, Singtel, UOB Ventures, Allianz, GoJek, Astra, Temasek and many more. We recently closed our Series C round and In total have raised around USD$180 million for our mission. Our team works tirelessly to make sure that we create the best healthcare solution personalised for all of our patient's needs, and are continuously on a path to simplify healthcare for Indonesia.

Vikesh Prasad

Software Development Engineer iOS

lllll