HTTP Client in Swift: Making Requests Properly from A to Z

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction



Modern iOS and macOS applications rarely function without interacting with remote servers. Whether it's loading data from a news feed, submitting a registration form, or synchronizing files — all these operations require a well-implemented HTTP client. In the Swift ecosystem, developers have access to the powerful built-in framework URLSession, which becomes more convenient every year thanks to the introduction of async/await.



In this article, we will explore how to properly create HTTP requests in Swift, starting from basic structures and ending with advanced techniques for error handling and data serialization. You will learn how to write clean, safe, and performant networking code for your applications.



URLSession Basics: Your First HTTP Request



The URLSession class is the foundation for any networking work in Swift. It manages configuration, caching, sessions, and the direct execution of requests. Let's look at a simple GET request to fetch data from a server.



Creating a Simple GET Request



import Foundation

// 1. Create a URLguard let url = URL(string: "https://api.example.com/users") else { print("Invalid URL") return}

// 2. Create a request (GET by default)var request = URLRequest(url: url)request.httpMethod = "GET"request.setValue("application/json", forHTTPHeaderField: "Accept")

// 3. Create a sessionlet session = URLSession.shared

// 4. Execute the request (old way with closure)let task = session.dataTask(with: request) { data, response, error in if let error = error { print("Request error: \\(error.localizedDescription)") return } guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else { print("Server returned an error") return } if let data = data, let jsonString = String(data: data, encoding: .utf8) { print("Received data: \\(jsonString)") }}task.resume() // Important! Start the task


Key points: always check the response status (200...299), don't forget to call resume(), and handle potential errors. In real projects, this code is usually extracted into a separate service layer.



Modern Approach: async/await



Starting with Swift 5.5, it became possible to use async/await, which makes the code much more readable and eliminates the "pyramid of closures".



func fetchUsers() async throws -> Data {    guard let url = URL(string: "https://api.example.com/users") else {        throw URLError(.badURL)    }        var request = URLRequest(url: url)    request.httpMethod = "GET"    request.setValue("application/json", forHTTPHeaderField: "Accept")        let (data, response) = try await URLSession.shared.data(for: request)        guard let httpResponse = response as? HTTPURLResponse,          (200...299).contains(httpResponse.statusCode) else {        throw URLError(.badServerResponse)    }        return data}


This code can be called from any asynchronous function or task, significantly simplifying the logic.



Working with JSON: Codable and Data Models



Data rarely comes in "raw" form. Most often, we deal with JSON. Swift offers an elegant solution — the Codable protocol (combining Encodable and Decodable).



Creating a Model and Decoding



// 1. Define the data structurestruct User: Codable {    let id: Int    let name: String    let email: String}

// 2. Asynchronous function to fetch an array of usersfunc fetchUsers() async throws -> [User] { let data = try await fetchUsersData() // from the previous example let decoder = JSONDecoder() return try decoder.decode

Blogs

Book Recommendations