Describe the process of converting a synchronous API call into an asynchronous one in Swift.

Instruction: Provide an example of transforming a synchronous function into an asynchronous one using Swift’s concurrency features.

Context: This question gauges the candidate’s ability to work with Swift’s concurrency model and their understanding of asynchronous programming patterns.

Updated

Official answer

If a library offers a native asynchronous operation, use it instead of merely adding async to a blocking function. For network loading, replace a synchronous Data(contentsOf:) call with URLSession's asynchronous API. The following complete definitions target iOS 15 or later and propagate transport, response and decoding errors:

import Foundation

struct UserData: Decodable, Sendable { let name: String }
enum FetchError: Error {
    case invalidURL
    case invalidResponse
    case httpStatus(Int)
}
func fetchUserData(
    from url: URL, session: URLSession = .shared
) async throws -> UserData {
    guard url.scheme?.lowercased() == "https", url.host != nil else {
        throw FetchError.invalidURL
    }
    try Task.checkCancellation()
    let (data, response) = try await session.data(for: URLRequest(url: url))
    guard let http = response as? HTTPURLResponse else {
        throw FetchError.invalidResponse
    }
    guard (200..<300).contains(http.statusCode) else {
        throw FetchError.httpStatus(http.statusCode)
    }
    try Task.checkCancellation()
    return try JSONDecoder().decode(UserData.self, from: data)
}

Call this from an async context with try await. A MainActor-isolated screen model should own its loading/error/success state and task lifetime. Network suspension lets other work proceed; decoding is synchronous work after the await, so bound response size and deliberately isolate expensive parsing if measurement requires it. Task creation does not itself guarantee background execution or a main-actor UI update.

For a callback-based API, a checked continuation can bridge it, but every path must resume exactly once and cancellation needs a separate design. For a truly blocking legacy API with no async equivalent, use a controlled blocking-work facility and a continuation only when necessary; a continuation alone does not move work off the calling executor. Avoid blocking cooperative executor threads or creating unbounded detached work.

Test successful JSON, non-HTTP responses, HTTP errors with otherwise valid JSON, malformed payloads and cancellation. HTTPS validation here is not a hostname allowlist or an authorization policy, and the example does not implement retries or streaming.

Asynchronous URLSession data, Swift concurrency

Related Questions