Instruction

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Eureka! You have an amazing idea and you know it’s going to be a success. You start by creating a new project in Xcode and get your UI built out. Then, it’s time for making an API request… so, what’s the best way to do that? After all, there are many ways to make an API request and retrieve the result.

The simplest approach is Swift’s new async/await structured concurrency. It provides a succinct and coherent code style for all your asynchronous needs.

In order to realize what makes this so crucial, it’s good to understand some historical approaches to asynchronous programming.

Historical Asynchronous Approaches

Before async/await, asynchronous programming relied on older paradigms and antiquated patterns.

Completion Handlers

One of the most common methods used was completion handlers. Completion handlers allowed you to pass in a parameter called a closure:

func fetchData(completion: @escaping (Data) -> Void) {
        let data = ... // Fetch data
        ... // Do other stuff
        completion(data)
    }
}
fetchData { data in
    print("Data: \(data)")
}

Grand Central Dispatch

Part of Grand Central Dispatch includes DispatchQueue. You could write the following code:

DispatchQueue.global().async {
    let data = ... // Fetch data
    ... // Do other stuff
    DispatchQueue.main.async {
        print("Data: \(data)")
    }
}

Understanding the Basics of Async/Await

Marking a function with async indicates that the function performs asynchronous work. To call an async function, you use the await keyword.

func fetchData() async -> String {
    await Task.sleep(2_000_000_000) // Simulates a delay of 2 seconds
    return "Data received"
}
Task {
    let data = await fetchData()
    print(data)
}
'async' property access in a function that does not support concurrency

Creating a Weather API Account

To show async/await in action, you’ll create an application that fetches weather data. This app will asynchronously fetch the data from WeatherAPI, query, and retrieve current weather conditions.

Weather API Setup
Geepkow EBU Bibuz

Setting Up the Weather Service

Open the starter project and go to the WeatherAppSample/Network/ folder. Open up WeatherService.swift. This file contains the following protocol:

protocol WeatherService {
  func getWeather(for query: String) async throws -> WeatherData
}
enum WeatherServiceError: Error {
  case invalidURL
}
class WAPIWeatherService: WeatherService {
  // 1
  private let apiKey = "<INSERT_API_KEY>"
  // 2
  private let baseUrl = "https://api.weatherapi.com/v1"
  private let currentWeatherPath = "/current.json"
  private let queryParamName = "q"
  private let keyParamName = "key"

  // 3
  func getWeather(for query: String) async throws -> WeatherData {
    var urlComponents = URLComponents(string: baseUrl + currentWeatherPath)
    urlComponents?.queryItems = [
      URLQueryItem(name: queryParamName, value: query),
      URLQueryItem(name: keyParamName, value: apiKey),
    ]

    guard let url = urlComponents?.url else {
      throw WeatherServiceError.invalidURL
    }

    // 4
    let (data, _) = try await URLSession.shared.data(from: url)
    let decoder = JSONDecoder()
    return try decoder.decode(WeatherData.self, from: data)
  }
}

Wiring Up the Repository

Next, you need to call the WeatherServiceImpl from the repository layer. Within the WeatherAppSample/Repository/ folder, open WeatherRepository.swift and note the protocol:

protocol WeatherRepository {
  func fetchWeather(for query: String) async throws -> WeatherData
}
func fetchWeather(for query: String) async throws -> WeatherData {
  // 1
  if let weatherData = weatherDataList[query] {
    return weatherData
  }

  // 2
  let weatherData = try await weatherService.getWeather(for: query)
  // 3
  weatherDataList[query] = weatherData
  // 4
  return weatherData
}

Setting Up the Presentation

This last part is to wire up the repository to the presentation layer. Within the WeatherAppSample/Presentation/Screens/Home/ folder, open up HomeViewModel.swift. In it, add a helper function to update the UI state:

func updateState(state: HomeState) {
  DispatchQueue.main.async {
    self.state = state
  }
}
func getWeather(query: String) {
  // 1
  state = .loading

  // 2
  Task {
    do {
      let weatherData = try await weatherRepo.fetchWeather(for: query)
      updateState(state: .ready(weatherData))
    } catch (_) {
      updateState(state: .error)
    }
  }
}

Testing the Implementation

Run the app to ensure that the weather data fetches and displays.

See forum comments
Download course materials from Github
Previous: Introduction Next: Conclusion