Swift Networking
URLSession with async/await
struct APIClient {
let baseURL: URL
let session: URLSession
init(baseURL: URL, session: URLSession = .shared) {
self.baseURL = baseURL
self.session = session
}
func get<T: Decodable>(_ path: String, as type: T.Type = T.self) async throws -> T {
let url = baseURL.appending(path: path)
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Accept")
let (data, response) = try await session.data(for: request)
try validate(response)
return try JSONDecoder.iso8601.decode(T.self, from: data)
}
func post<Body: Encodable, Response: Decodable>(_ path: String, body: Body) async throws -> Response {
let url = baseURL.appending(path: path)
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONEncoder().encode(body)
let (data, response) = try await session.data(for: request)
try validate(response)
return try JSONDecoder.iso8601.decode(Response.self, from: data)
}
private func validate(_ response: URLResponse) throws {
guard let http = response as? HTTPURLResponse else { throw APIError.invalidResponse }
guard (200..<300).contains(http.statusCode) else {
throw APIError.httpError(statusCode: http.statusCode)
}
}
}
enum APIError: LocalizedError {
case invalidResponse
case httpError(statusCode: Int)
var errorDescription: String? {
switch self {
case .invalidResponse: return "Invalid server response"
case .httpError(let code): return "HTTP error \(code)"
}
}
}
extension JSONDecoder {
static let iso8601: JSONDecoder = {
let d = JSONDecoder()
d.dateDecodingStrategy = .iso8601
return d
}()
}
Codable / JSON Decoding
struct Article: Codable {
let id: Int
let title: String
let publishedAt: Date
let author: Author
// Custom key mapping
enum CodingKeys: String, CodingKey {
case id, title, author
case publishedAt = "published_at"
}
}
struct Author: Codable {
let id: Int
let name: String
let avatarURL: URL
enum CodingKeys: String, CodingKey {
case id, name
case avatarURL = "avatar_url"
}
}
Retry with Backoff
func withRetry<T>(
maxAttempts: Int = 3,
initialDelay: Duration = .seconds(1),
operation: () async throws -> T
) async throws -> T {
var delay = initialDelay
for attempt in 1...maxAttempts {
do {
return try await operation()
} catch where attempt < maxAttempts {
try await Task.sleep(for: delay)
delay = delay * 2 // exponential backoff
}
}
return try await operation() // final attempt propagates error
}
Multipart Upload
func upload(image: Data, filename: String) async throws -> UploadResponse {
let boundary = UUID().uuidString
var request = URLRequest(url: uploadURL)
request.httpMethod = "POST"
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
var body = Data()
body.append("--\(boundary)\r\n".data(using: .utf8)!)
body.append("Content-Disposition: form-data; name=\"file\"; filename=\"\(filename)\"\r\n".data(using: .utf8)!)
body.append("Content-Type: image/jpeg\r\n\r\n".data(using: .utf8)!)
body.append(image)
body.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)
request.httpBody = body
let (data, _) = try await URLSession.shared.data(for: request)
return try JSONDecoder().decode(UploadResponse.self, from: data)
}
WebSocket
actor WebSocketManager {
private var task: URLSessionWebSocketTask?
func connect(url: URL) {
task = URLSession.shared.webSocketTask(with: url)
task?.resume()
receiveLoop()
}
func send(_ text: String) async throws {
try await task?.send(.string(text))
}
private func receiveLoop() {
Task {
while let task {
do {
let message = try await task.receive()
switch message {
case .string(let text): handleText(text)
case .data(let data): handleData(data)
@unknown default: break
}
} catch {
break
}
}
}
}
}
Common Anti-Patterns
- Synchronous network calls — always use async/await; never block the main thread
- Force decoding — let
JSONDecoder throw; don't catch and return nil
- Ignoring HTTP status codes — validate response before decoding body
- Hardcoded base URLs — inject via configuration or environment
- No retry on transient failures — implement exponential backoff for 5xx/timeout errors