Modern Swift makes network requests easier to read with async and await. URLSession provides asynchronous methods that return data and a response without completion handlers.

Creating a Network Model

The response model should conform to Decodable:

struct Todo: Decodable, Identifiable {
    let id: Int
    let title: String
    let completed: Bool
}

Fetching Data

Create a request, validate the response, and decode the returned data:

enum NetworkError: Error {
    case invalidResponse
    case requestFailed(Int)
}

func fetchTodos() async throws -> [Todo] {
    let url = URL(string: "https://example.com/todos")!
    let (data, response) = try await URLSession.shared.data(from: url)

    guard let response = response as? HTTPURLResponse else {
        throw NetworkError.invalidResponse
    }

    guard 200..<300 ~= response.statusCode else {
        throw NetworkError.requestFailed(response.statusCode)
    }

    return try JSONDecoder().decode([Todo].self, from: data)
}

Always check the HTTP status code. A request can complete successfully while the server still returns an error such as 404 or 500.

Loading Data in SwiftUI

A view model can perform the request and update the interface:

@MainActor
@Observable
final class TodoStore {
    var todos: [Todo] = []
    var errorMessage: String?

    func load() async {
        do {
            todos = try await fetchTodos()
        } catch {
            errorMessage = error.localizedDescription
        }
    }
}

Use the store from a SwiftUI view:

struct TodoList: View {
    @State private var store = TodoStore()

    var body: some View {
        List(store.todos) { todo in
            Text(todo.title)
        }
        .task {
            await store.load()
        }
    }
}

The task modifier automatically cancels its task when the view disappears.

Sending JSON

Use URLRequest when you need to configure the HTTP method or body:

var request = URLRequest(url: URL(string: "https://example.com/todos")!)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONEncoder().encode(todo)

let (_, response) = try await URLSession.shared.data(for: request)

Wrap up

Using URLSession with async/await keeps networking code linear and easy to follow. Validate responses, decode into dedicated models, and keep user interface updates on the main actor.

Resources:

Keep reading

Read more

2 min read Pull-to-Refresh in SwiftUI Read article 3 min read Xcode shortcuts Read article 11 min read Keyboard Navigation Read article

Share

Share this article

Share Bluesky Mastodon Twitter LinkedIn Facebook