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: