Labs ICT
Pro Login

Networking

Fetching data from APIs.

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 ->