Migrating the Network layer with Swift Concurrency by replacing the PromiseKit

Apr 1, 2024

At Halodoc, we have a relentless commitment to staying at the forefront of technology, always seeking out opportunities to enhance our tech stack for improved efficiency and performance. One key item on our agenda is migrating towards Swift's async/await paradigm for concurrency, a move that promises significant enhancements to our development workflow and overall codebase.

Introduction

We started off our app's upgrade with the network layer by adopting Swift's built-in "async/await" instead of PromiseKit. This modernisation promises cleaner code, smoother error handling, and a faster network experience.

Background

We built our Network layer using Alamofire & PromiseKit.

  • Alamofire streamlines network requests, minimizing code compared to URLSession.
  • PromiseKit enhances readability and maintainability by simplifying asynchronous operations.

By integrating these tools, our codebase became more efficient, readable, and adaptable, ensuring smoother development and easier maintenance.

Swift's async/await

Async/await is a programming pattern that was introduced in Swift 5.5 to simplify asynchronous programming by allowing developers to write asynchronous code in a more synchronous manner. It's a feature that enables writing asynchronous code that looks and behaves more like synchronous code, making it easier to understand and maintain.

How does it work?

  1. Mark functions that do asynchronous tasks with async.
  2. When calling an asynchronous function, use await before it. This tells Swift to wait for the result before continuing.
  3. If a async functions throws errors, then mark it with async throws
  4. While waiting for an async task, other parts of your code can continue running.

For a comprehensive exploration look into our Swift concurrency blog.

Refactoring the Network Manager

We approached the refactoring and migration process in two phases,

Phase 1:

Introducing "AsyncApiClient": Seamlessly Integrating Swift async/await with Existing PromiseKit-based ApiClient:

This approach priorities the creation of AsyncApiClient to bridge the gap between PromiseKit and Swift's async/await, enhancing the Network Manager's functionality without disrupting its existing infrastructure.

  • Introduced a new component named AsyncApiClient to modernize asynchronous operations using Swift's async/await.
  • Despite internal workings with existing PromiseKit's ApiClient, externally expose a cleaner, async/await-compatible interface.

Phase 2:

Enhancing "AsyncApiClient": Transitioning to pure Swift async/await with "Alamofire"

We advanced the capabilities of AsyncApiClient by transitioning its internal workings from the PromiseKit-based ApiClient to pure Swift async/await syntax, leveraging Alamofire for network requests.

PromiseKit vs Swift async/await

The below code snippet illustrates how Swift's async/await syntax simplifies asynchronous code, improving readability, error handling, and maintainability compared to PromiseKit's chaining syntax. Additionally, its native integration enhances compatibility and adoption within Swift projects.

PromiseKit
async/await

Advantages of Swift's async/await Over PromiseKit:

  1. Simplified Syntax: Offers a more intuitive and readable approach to asynchronous coding by allowing developers to write it in a more linear and sequential manner, reducing the need for nested callbacks or chaining promises.
  2. Native Integration: Seamless compatibility and adoption within Swift projects without additional dependencies.This native integration ensures better performance and compatibility with other Swift language features.
  3. Error Handling: Utilizes familiar try-catch mechanisms for simpler and consistent error handling, ensuring structured error handling across the app.
  4. Concurrency Support: Built-in concurrency support for improved performance, responsiveness and parallel execution allowing to write asynchronous code that can take full advantage of multicore processors
  5. Standardization and Future-proofing: Aligns with modern programming standards and ensures compatibility with future Swift updates.This native approach future-proofs codebases and reduces the risk of dependency issues or API changes.
  6. Reduce app size: By eliminating the dependency on the PromiseKit library, developers can reduce app bundle size which can leading to faster download times and decreased memory footprint on users' devices.
  7. Debugging: Better support for debugging asynchronous code by integrating seamlessly with Swift's debugging tools. It offers clearer stack traces and error messages, aiding developers in identifying and resolving issues more efficiently.

Potential Challenges

  • Rewriting Unit Tests: Existing tests based on PromiseKit callbacks need adjustment for async/await syntax.
  • Caution with "Task" Usage:  It may lead to blocking if asynchronous tasks are invoked within synchronous functions, especially in UI contexts. Manage task execution carefully, switch to the main thread for UI updates to avoid freezes.
  • Compatibility: Ensure seamless integration with existing code and third-party libraries.
  • Error Handling Differences: Adapt error handling strategies to async/await mechanism.

Conclusion

Overall, the adoption of Swift's async/await represented a significant step forward in modernising our app's Network Layer, enhancing its readability, and maintainability while ensuring compatibility with future Swift updates. By addressing potential challenges and leveraging the advantages of async/await, we have achieved a more efficient and robust network infrastructure for our apps.

References

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 D round and In total have raised around USD $100+ 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.

M Shyam Kumar

Software Development Engineer @ Halodoc