Making a network request

Networking Junior Swift 5.5+ iOS 15+ Reviewed By Alok Singh

Spot the bug

When the server returns 404, users see a confusing decoding error. Tap the line to fix.

  1. func loadUser(id: Int) async throws -> User {
  2. let url = URL(string: "https://api.example.com/users/\(id)")!
  3. let (data, _) = try await URLSession.shared.data(from: url)
  4. return try JSONDecoder().decode(User.self, from: data)
  5. }
Hint 1 of 1

Does URLSession throw for HTTP errors?

Choose an answer, then open Show answer below to check it.

Show answer
  • Answer: line 3 let (data, _) = try await URLSession.shared.data(from: url)

Line 3 throws away the response, so a 404 body is decoded as a User. Keep the response, check (response as? HTTPURLResponse)?.statusCode is 200...299, and throw a clear error otherwise.

30-second answer

Use URLSession's async API: let (data, response) = try await URLSession.shared.data(from: url). Then check that the response is an HTTPURLResponse with a 2xx status before decoding the body with JSONDecoder. Throw typed errors for bad statuses so callers can show the right message or retry.

Practise saying it · 30 s. No recording, just a timer

Explain how you make and validate a request.

Say your answer out loud, then check which of these points you covered:

  • async data(from:)
  • Check HTTP status
  • Decode and map errors

Deep dive

URLSession throws only for transport errors; 404 and 500 arrive as normal responses.

Async URLSession calls respect Task cancellation.

Build URLs with URLComponents to escape query items.

Decode off the main actor for large payloads.

Validated request Type-checked · Swift 6.4
enum APIError: Error { case badStatus(Int) }

func loadUser(id: Int) async throws -> User {
    var components = URLComponents(string: "https://api.example.com/users")!
    components.path += "/\(id)"
    let (data, response) = try await URLSession.shared.data(from: components.url!)
    guard let http = response as? HTTPURLResponse else { throw URLError(.badServerResponse) }
    guard (200...299).contains(http.statusCode) else { throw APIError.badStatus(http.statusCode) }
    return try JSONDecoder().decode(User.self, from: data)
}

Common mistakes

  • Ignoring the status code.
  • Force-unwrapping URLs built from user input.

Senior take & follow-ups

Seniors validate responses and map errors into a domain error type.

Follow-up questions

Think of your answer first, then open the hint.

  1. Does a 500 throw?

    Show hint

    No; you must check the status code yourself.

  2. How do you cancel a request?

    Show hint

    Cancel the task that awaits it.

Suggest a fix if something here is wrong or out of date.