Networking in Depth: REST APIs, Codable & Error Handling
iOS Development — Architecture & Data
Chapter 3 · Networking in Depth: REST APIs, Codable & Error Handling
Fundamentals Chapter 9 covered a single, real GET request. Real apps need to send data too — creating, updating, deleting — and need real, descriptive errors when something goes wrong, not a generic failure. This chapter covers both, plus a small architectural pattern for keeping networking code out of ViewModels entirely.
URLRequest: Full Control Over an HTTP Request
URLSession.shared.data(from:) — Fundamentals' own tool — only issues a plain GET.
URLRequest is the real, general-purpose struct needed for anything else:
setting the HTTP method, real custom headers, and a real request body.
| Tool | Real Capability |
|---|---|
URLSession.shared.data(from: url) | GET only, no headers, no body — Fundamentals' own simple case |
URLSession.shared.data(for: request) | Any real HTTP method, real custom headers, a real request body via URLRequest |
Encoding a Request Body with Encodable
Fundamentals used Decodable to turn JSON into a struct. Sending data back needs the reverse
— Encodable, or the combined Codable typealias covering both directions at once:
Real JSON Key Mismatches: CodingKeys & .convertFromSnakeCase
Real-world APIs rarely use Swift's own camelCase convention — snake_case is genuinely common.
Two real, complementary tools handle this:
.convertFromSnakeCase is genuinely convenient when an entire API consistently uses
snake_case — one line, every property. CodingKeys is the real fallback for
exceptions: a specific key that doesn't follow the pattern, or a Swift property name that needs to differ
from its JSON key for a reason beyond casing alone.
Real, Descriptive Errors
Throwing a generic URLError for every possible failure tells a caller almost nothing useful.
A real, custom error type conforming to LocalizedError can carry genuine, specific meaning:
APIError.requestFailed(statusCode: 404) can show a genuinely specific,
useful message — "Task not found" — instead of a generic "Something went wrong," and can react differently
to a 401 (real, likely an expired login) than a 500 (a real server problem, not
the user's own fault).
A Centralized APIClient
Repeating URLSession/JSONDecoder/status-code-checking logic inside every
ViewModel is real, genuine duplication. A small, shared APIClient — following Chapter 1's own
separation-of-concerns discipline — centralizes it once:
200..<300, Not Just == 200201 Created, 204 No Content — not just a plain 200. Checking a
real range rather than one exact number is the genuinely correct check.
fetch's own generic <T: Decodable> means one real function serves every
model type in the app — apiClient.fetch(Quote.self, from: quoteURL),
apiClient.fetch([Task].self, from: tasksURL) — with the status-code checking and error
handling written exactly once.
Hands-On Exercises
Write a real function createTask(_ newTask: NewTaskRequest) async throws -> Task that builds a URLRequest with method "POST", a JSON-encoded body via JSONEncoder, and decodes the server's own response back into a Task.
Add a real case .unauthorized to APIError, with a matching errorDescription, and update the chapter's own fetch function so a 401 status code throws .unauthorized specifically, rather than the generic .requestFailed(statusCode:) case.
Explain, in your own words, why centralizing status-code checking and decoding inside one APIClient, rather than repeating that logic inside every individual ViewModel's own networking code, is a genuine real-world maintenance win — connecting your answer to Chapter 1's own MVVM separation-of-concerns principle.
Chapter 3 Quick Reference
URLRequest— real, full control over HTTP method, headers, and body, needed for anything beyond a plain GETEncodable/JSONEncoder— turns a real Swift struct into a JSON request body.convertFromSnakeCase(automatic) andCodingKeys(manual, per-property) — two real, complementary tools for JSON key mismatches- A custom
Errorconforming toLocalizedErrorcarries real, specific meaning a generic error can't - A centralized, generic
APIClientavoids repeating status-code/decoding logic inside every ViewModel