Asynchronous Programming Fundamentals
In this lesson, we’ll go over the current situation with asynchronous programming, and see how it is prone to error and makes the lives of developers difficult.
Getting Started
First of all, download the starter project for Apple News by cloning the Materials repo or by downloading the Starter folder under 01-the-power-of-async-await.
Before running the app, you need to generate a developer’s API key for NewsAPI.
Generating a News API Key
The Apple News app uses the NewsAPI engine to retrieve news from the Apple world.
NewsAPI provides developers access to a vast repository of news articles, headlines, and related information from various sources, such as news organizations, blogs, and publications.
The API is free to use for development purposes, but you need to register to get a key.
To register, go to News API and click Get API Key.
In the following screen, enter your details, and click Submit.
NewsAPI generates a new API key for you, which is visible on your account page.
Once you have your API key, open the Xcode project (AppleNews.xcodeproj), and replace the placeholder in the file NewsService.Swift:
static let apiKey = "<ADD_YOUR_KEY_HERE>"
Build and run the project, and you’ll see the app’s initial screen. Tap Load Latest News to see the latest Apple-centric news.
Project Organization
The following diagram presents the overall project architecture.
The app makes an HTTP GET
request to NewsAPI to retrieve the latest news,
and the NewsAPI server returns a JSON with the content.
The JSON response contains an array of article objects.
The app decodes the JSON using the standard decoder and presents the
results in a list view.
Here are the main project files you’ll interact with most:
-
Article
contains a model for the object in the JSON response. -
NewsService
abstracts the API service managing the request-response flow. -
NewsViewModel
interfaces the service with the view. -
NewsView
is the main view modeled in SwiftUI.
Why Use Asynchronous Programming
In any modern app, especially those with rich and dynamic user interfaces, numerous tasks, such as data fetching, processing, and updating, must occur simultaneously without hindering the user experience.
Without concurrency, executing long-running operations (on the main thread) would lead to unresponsive UIs, causing sluggishness or even freezes, ultimately frustrating users.
To give you some numbers: With a frame refresh of 60 frames per second, the app must generate a new frame every ~167ms. A network response can take some seconds to be received. Think about what would happen if your app’s UI is frozen, waiting for the network response to be received and processed.
By leveraging concurrency techniques, you can delegate time-consuming tasks to background threads, ensuring that the main thread remains responsive to user interactions. This not only enhances the app’s overall responsiveness and performance, but also leads to a smoother and more engaging user experience, which is vital for retaining and engaging users in today’s competitive app landscape.
Issues with the “Traditional” Asynchronous Code
Now that you understand the rationale behind asynchronous code, you’ll
focus on the NewsService.swift file, specifically on
the function latestNews(_:)
that fetches the news once the user taps Load Latest News.
This function uses the URLSession
traditional API based on a completion
handler.
As you can see, the execution flow is not linear like in the traditional synchronous programming, where the CPU executes the instructions sequentially:
- First, you define a
URLSession
task, passing a completion handler that takes either an array of articles or an error. This handler will be executed later, once the network response is received. - Then, you start the task, which triggers the network request to be sent to the server.
- On receiving a response, the execution resumes within the completion handler.
While effective, this pattern comes with some well-known disadvantages that can impact code readability, maintainability, and your productivity, especially in complex asynchronous scenarios. Among these, some of the most noteworthy are:
- Callback hell: When nesting multiple completion handlers.
- Inversion of control: Meaning that the completion handler, defined at the top, is executed just at the end when the control returns.
- Error handling: Managing errors with completion handlers can be cumbersome, as errors must be explicitly passed along through their parameters.
- Prone to error: Since there is no way to enforce the completion handler is called for every branch of the execution.
In the second part of the lesson, you’ll see how incorporating async/await into your codebase will result in a streamlined execution flow that enhances readability and simplifies error handling. Compile-time checks ensure that your code consistently delivers results, effectively minimizing the chance of subtle errors such as the one above.