URLSession for HTTP Requests
URLSession is Swift's built-in networking tool. It handles HTTP requests — GET, POST, PUT, DELETE — and comes with automatic caching, cookie management, and background transfers. Combined with async/await, networking is clean and readable.
func fetchTodos() async throws -> [Todo] {
let url = URL(string: "https://jsonplaceholder.typicode.com/todos")!
let (data, response) = try await URLSession.shared.data(from: url)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw URLError(.badServerResponse)
}
return try JSONDecoder().decode([Todo].self, from: data)
}
Try it Yourself ->
async/await with URLSession
URLSession has built-in async methods. Call data(from:) with await and you get back both the data and the response. No completion handlers, no callbacks — just straightforward code.
func searchMovies(query: String) async throws -> [Movie] {
var components = URLComponents(string: "https://api.example.com/movies")!
components.queryItems = [URLQueryItem(name: "q", value: query)]
let (data, _) = try await URLSession.shared.data(from: components.url!)
let result = try JSONDecoder().decode(MovieResponse.self, from: data)
return result.movies
}
Try it Yourself ->
Decodable Protocol for JSON Parsing
Conform your model to Decodable and JSONDecoder does the heavy lifting. Map JSON keys to property names automatically, or use CodingKeys for custom mappings.
struct Todo: Decodable {
let userId: Int
let id: Int
let title: String
let completed: Bool
}
struct UserProfile: Decodable {
let name: String
let email: String
enum CodingKeys: String, CodingKey {
case name = "full_name"
case email
}
}
Try it Yourself ->
Error Handling Network Requests
Network calls can fail for many reasons — no connection, bad URL, server errors. Wrap them in do/try/catch and handle each case. Show the user what went wrong and let them retry.
struct NetworkManager {
enum NetworkError: Error, LocalizedError {
case invalidURL
case serverError(statusCode: Int)
case decodingError
case noConnection
var errorDescription: String? {
switch self {
case .invalidURL: return "Invalid URL"
case .serverError(let code): return "Server error: \(code)"
case .decodingError: return "Failed to parse response"
case .noConnection: return "No internet connection"
}
}
}
func fetch(_ type: T.Type, from url: URL) async throws -> T {
let (data, response) = try await URLSession.shared.data(from: url)
guard let http = response as? HTTPURLResponse,
(200...299).contains(http.statusCode) else {
throw NetworkError.serverError(statusCode: (response as? HTTPURLResponse)?.statusCode ?? 0)
}
return try JSONDecoder().decode(T.self, from: data)
}
}
Try it Yourself ->
Showing Loading States
Users need feedback while data loads. Track loading state with a boolean and show a progress view. Hide it when the data arrives or an error occurs.
struct UserListView: View {
@State private var users: [User] = []
@State private var isLoading = false
@State private var errorMessage: String?
var body: some View {
Group {
if isLoading {
ProgressView("Loading users...")
} else if let error = errorMessage {
VStack {
Text(error)
.foregroundColor(.red)
Button("Retry") { Task { await loadUsers() } }
}
} else {
List(users) { user in
Text(user.name)
}
}
}
.task { await loadUsers() }
}
func loadUsers() async {
isLoading = true
errorMessage = nil
do {
users = try await NetworkManager().fetch([User].self,
from: URL(string: "https://api.example.com/users")!)
} catch {
errorMessage = error.localizedDescription
}
isLoading = false
}
}
Try it Yourself ->
Codable for Encoding and Decoding
Codable is just Decodable & Encodable combined. Use it when you need to both read and write JSON — like sending data to an API.
struct CreateUser: Codable {
let name: String
let email: String
}
func createUser(_ user: CreateUser) async throws -> User {
let url = URL(string: "https://api.example.com/users")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONEncoder().encode(user)
let (data, _) = try await URLSession.shared.data(for: request)
return try JSONDecoder().decode(User.self, from: data)
}
Try it Yourself ->