Introduction: Why Do Swift Developers Still Google HTTP Requests?
If you've ever written a Swift app that communicates with a server, you've definitely encountered this question. It seems simple, right? Send a request, get a response. But in practice, every second developer (even experienced ones) opens Stack Overflow and searches: "How to make HTTP request in Swift?". And that's normal.
Why? Because the Swift ecosystem is changing. Yesterday everyone was writing closures and delegates. Today it's async/await. And tomorrow, something else might appear. In 2025, approaches to network requests have stabilized, but the devil, as always, is in the details: thread management, error handling, request cancellation, testing.
In this article, we'll cover real best practices for making HTTP requests in Swift — from basic URLSession to advanced techniques with async/await and libraries. You'll learn how to write clean, safe, and performant networking code. Let's go!
1. URLSession: The Foundation Everyone Should Know
Let's start with the main thing. The Swift standard library (Foundation) has everything you need for working with HTTP — the URLSession class. Remember: for 90% of tasks, you don't need third-party libraries. URLSession is a powerful, flexible, and native tool.
1.1. Simple GET Request (Old Style)
Let's recall how it was done before. You might still encounter this code in legacy projects:
let url = URL(string: "https://api.pythonlib.ru/users")!let task = URLSession.shared.dataTask(with: url) { data, response, error in if let error = error { print("Error: \\(error.localizedDescription)") return } guard let data = data else { print("No data") return } // Work with data print("Bytes received: \\(data.count)")}task.resume()This code works. But it has drawbacks:
- Callback hell — if you need to chain requests, the code becomes unreadable.
- Memory leaks — it's easy to forget
weak selfin the closure. - Hard to cancel — you need to keep a reference to the
task.
In 2025, we write differently.
1.2. Modern GET Request with async/await
Starting with Swift 5.5 (iOS 15, macOS 12), we have structured concurrency. This changes everything:
func fetchUsers() async throws -> [User] { let url = URL(string: "https://api.pythonlib.ru/users")! let (data, response) = try await URLSession.shared.data(from: url) guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else { throw URLError(.badServerResponse) } return try JSONDecoder().decode([User].self, from: data)}Beautiful, right? No closures, no resume(). The code reads like synchronous code but executes asynchronously. This is the de facto standard in 2025.
2. POST Request: Sending Data to the Server
Now let's send some data. Suppose we need to create a new user.
2.1. POST with JSON Body
func createUser(name: String, email: String) async throws -> User { let url = URL(string: "https://api.pythonlib.ru/users")! var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") let body = ["name": name, "email": email] request.httpBody = try JSONSerialization.data(withJSONObject: body) let (data, response) = try await URLSession.shared.data(for: request) // Handle response... return try JSONDecoder().decode(User.self, from: data)}
Blogs
Book Recommendations
Read also