Swift
iOS Development
Asynchronous Programming
Functions
Swift Concurrency

iOS - Swift - Function that returns asynchronously retrieved value

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

A Swift function cannot synchronously return a value that has not arrived yet from asynchronous work such as a network request. The real solution is to change the API shape: use a completion handler or make the function itself async so the caller waits correctly.

Why a Direct Return Does Not Work

This is the mistake people usually try first:

swift
1func loadName() -> String {
2    var result = ""
3
4    URLSession.shared.dataTask(with: URL(string: "https://example.com")!) { data, _, _ in
5        result = "Ava"
6    }.resume()
7
8    return result
9}

The function returns before the network callback runs, so result is returned too early. That is not a Swift quirk. It is the normal behavior of asynchronous execution.

Completion Handler Approach

A traditional fix is to return through a closure.

swift
1import Foundation
2
3func loadName(completion: @escaping (Result<String, Error>) -> Void) {
4    let url = URL(string: "https://example.com")!
5
6    URLSession.shared.dataTask(with: url) { data, _, error in
7        if let error = error {
8            completion(.failure(error))
9            return
10        }
11
12        completion(.success("Ava"))
13    }.resume()
14}

Caller side:

swift
1loadName { result in
2    switch result {
3    case .success(let name):
4        print(name)
5    case .failure(let error):
6        print(error)
7    }
8}

This makes the asynchronous nature explicit instead of pretending the value is immediately available.

async and await Approach

Modern Swift concurrency gives a cleaner syntax for the same idea.

swift
1import Foundation
2
3func loadName() async throws -> String {
4    let url = URL(string: "https://example.com")!
5    let (_, _) = try await URLSession.shared.data(from: url)
6    return "Ava"
7}

Caller side:

swift
1Task {
2    do {
3        let name = try await loadName()
4        print(name)
5    } catch {
6        print(error)
7    }
8}

This still is not synchronous. It simply makes asynchronous code read more clearly.

Update the UI on the Main Actor

If the asynchronously retrieved value updates UI state, make sure the UI work happens on the main actor.

swift
1import UIKit
2
3@MainActor
4func showName(label: UILabel) async {
5    do {
6        let name = try await loadName()
7        label.text = name
8    } catch {
9        label.text = "Error"
10    }
11}

The networking can happen asynchronously, but UI updates must still follow UIKit rules.

Design the API Around Timing

The core design principle is simple: a function should advertise whether it produces a value now or later.

Use:

  • A direct return value for immediately available data.
  • A completion handler for callback-style asynchronous work.
  • 'async for modern structured concurrency.'

Trying to hide delayed work behind a direct synchronous return almost always creates bugs.

Legacy APIs Can Be Wrapped

If you are stuck with a completion-handler API, you do not need to rewrite the whole networking layer at once. You can bridge older callbacks into async functions gradually and keep the call sites cleaner over time.

Common Pitfalls

  • Trying to return a value before the asynchronous callback has run.
  • Mixing completion handlers and synchronous return values in the same design.
  • Forgetting to propagate errors from asynchronous work.
  • Updating UIKit state from the wrong execution context.
  • Treating async as magic synchronous behavior rather than as explicit suspension.

Summary

  • A function cannot synchronously return a value that will only exist later.
  • Use a completion handler or an async function instead.
  • The caller must also participate in the asynchronous flow.
  • Keep UI updates on the main actor.
  • The right fix is to redesign the API around the timing of the data, not to fight the timing model.

Course illustration
Course illustration